npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@groo.dev/auth-server

v0.7.2

Published

Server-side authentication middleware for Groo Auth SDK

Readme

@groo.dev/auth-server

Server-side authentication middleware and utilities for Hono and Cloudflare Workers.

Installation

npm install @groo.dev/auth-server hono

Features

  • Unified Authentication - Single initialization for middleware and M2M operations
  • User Authentication Middleware - Validate session cookies and protect routes
  • API Token Authentication - Verify API tokens for machine-to-machine (M2M) requests
  • Token Management - Programmatically create, list, and revoke API tokens
  • Auth Routes Proxy - Handle /__auth/me endpoint for frontend SDKs
  • Machine-to-Machine Client - Access consented users via client credentials
  • App Data - Store and retrieve app-specific data for users and tokens

Quick Start

import { Hono } from 'hono'
import { grooAuth } from '@groo.dev/auth-server'
import { GrooHonoMiddleware } from '@groo.dev/auth-server/hono'

type Env = {
  CLIENT_ID: string
  CLIENT_SECRET: string
}

// Create middleware with factory (env available at request time)
const hono = new GrooHonoMiddleware<Env>((env) => grooAuth({
  clientId: env.CLIENT_ID,
  clientSecret: env.CLIENT_SECRET,
}))

const app = new Hono<{ Bindings: Env }>()

// Initialize groo in context (required - must be first)
app.use('*', hono.init)

// Mount auth routes for frontend SDK (handles /__auth/me)
app.route('/v1', hono.routes)

// Protected route - requires authentication AND consent
app.get('/v1/profile', hono.middleware, (c) => {
  const user = c.get('user')  // ConsentedUser with appData
  return c.json({ user })
})

// M2M operations - use c.get('groo')
app.get('/v1/admin/users', hono.middleware, async (c) => {
  const user = c.get('user')
  if (user?.role !== 'admin') {
    return c.json({ error: 'Forbidden' }, 403)
  }
  const groo = c.get('groo')
  const result = await groo.getUsers()
  return c.json(result)
})

export default app

API Reference

grooAuth(config)

Creates a core authentication instance for session validation and M2M operations.

import { grooAuth } from '@groo.dev/auth-server'

const groo = grooAuth({
  clientId: 'your-client-id',      // Required
  clientSecret: 'your-client-secret',  // Required
  baseUrl: 'https://accounts.groo.dev',  // Optional (default)
  cookieName: 'session',  // Optional (default)
})

GrooHonoMiddleware

Creates Hono-specific middleware with a factory function for Cloudflare Workers environment.

import { GrooHonoMiddleware } from '@groo.dev/auth-server/hono'

type Env = { CLIENT_ID: string; CLIENT_SECRET: string }

const hono = new GrooHonoMiddleware<Env>((env) => grooAuth({
  clientId: env.CLIENT_ID,
  clientSecret: env.CLIENT_SECRET,
}))

hono.init

Middleware that initializes groo in context. Must be called first with app.use('*', hono.init).

app.use('*', hono.init)  // Required - enables c.get('groo')

hono.middleware

Middleware that requires authentication AND consent. Returns 401 if not authenticated.

app.get('/api/protected', hono.middleware, (c) => {
  const user = c.get('user')  // ConsentedUser with consent.appData
  return c.json({ user })
})

hono.optionalMiddleware

Middleware that adds user to context but doesn't require authentication.

app.get('/api/optional', hono.optionalMiddleware, (c) => {
  const user = c.get('user')  // ConsentedUser or null
  return c.json({ authenticated: !!user })
})

hono.routes

A Hono router that handles authentication routes for frontend SDKs. Returns 401 if not authenticated.

// Mount at /v1 to handle /v1/__auth/me (requires authentication)
app.route('/v1', hono.routes)

Context Variables

After hono.init is called, these are available in all routes:

c.get('groo')      // GrooAuth instance for M2M operations
c.get('user')      // ConsentedUser (after middleware) or null
c.get('apiToken')  // ApiTokenInfo (after apiTokenMiddleware) or null

API Token Authentication (M2M)

API tokens allow external services (GitHub Actions, cron jobs, other backends) to authenticate with your API without user sessions.

Flow

  1. Create an API token in the Groo Accounts dashboard
  2. Give the token to your external service (e.g., GitHub Actions secret)
  3. External service sends requests with Authorization: Bearer groo_xxxx
  4. Your API verifies the token via apiTokenMiddleware

Example: Webhook Endpoint

// Protected by API token (not user session)
app.post('/v1/webhook', hono.apiTokenMiddleware, (c) => {
  const token = c.get('apiToken')
  console.log(`Request from: ${token.application_name} / ${token.token_name}`)
  return c.json({ received: true })
})

// GitHub Action can call this with:
// curl -X POST https://api.myapp.com/v1/webhook \
//   -H "Authorization: Bearer groo_xxxx"

hono.apiTokenMiddleware

Middleware that requires a valid API token. Returns 401 if token is missing or invalid.

app.post('/v1/internal/sync', hono.apiTokenMiddleware, (c) => {
  const token = c.get('apiToken')  // ApiTokenInfo
  return c.json({
    application: token.application_name,
    token: token.token_name,
  })
})

ApiTokenInfo Object

interface ApiTokenInfo {
  active: true
  client_id: string
  token_type: 'Bearer'
  exp?: number              // Expiration timestamp (if set)
  iat: number               // Issued at timestamp
  sub: string               // Token ID
  aud: string               // Client ID
  application_id: string
  application_name: string
  token_name: string
  token_description: string | null
  app_data: Record<string, unknown>  // Token-specific custom data
}

interface ApiToken<T = Record<string, unknown>> {
  id: string
  applicationId: string
  name: string
  description: string | null
  tokenPrefix: string        // Last 4 chars (e.g., "a1b2")
  createdBy: string | null   // User ID if created from dashboard, null if M2M
  lastUsed: string | null
  expiresAt: string | null
  expired: boolean
  revoked: boolean
  appData: T
  createdAt: string
}

Using Token App Data

Each token can store custom metadata (e.g., rate limits, permissions, environment):

app.post('/v1/webhook', hono.apiTokenMiddleware, (c) => {
  const token = c.get('apiToken')

  // Access token-specific configuration
  const rateLimit = token.app_data.rate_limit as number || 1000
  const environment = token.app_data.environment as string || 'production'

  console.log(`Token ${token.token_name}: rate_limit=${rateLimit}, env=${environment}`)

  return c.json({ received: true })
})

Direct Token Verification

You can also verify tokens manually without middleware:

app.post('/v1/custom', async (c) => {
  const groo = c.get('groo')
  const authHeader = c.req.header('Authorization')

  if (authHeader?.startsWith('Bearer ')) {
    const token = authHeader.slice(7)
    const tokenInfo = await groo.verifyApiToken(token)

    if (tokenInfo) {
      // Token is valid
      return c.json({ valid: true, app: tokenInfo.application_name })
    }
  }

  return c.json({ error: 'Unauthorized' }, 401)
})

M2M Operations (via c.get('groo'))

app.get('/v1/admin/users', hono.middleware, async (c) => {
  const groo = c.get('groo')

  // List all consented users
  const { users, total } = await groo.getUsers({ page: 1, perPage: 20 })

  // Get specific user by ID
  const user = await groo.getUser('user-id')

  // Get user by email
  const userByEmail = await groo.getUserByEmail('[email protected]')

  // Get user by phone
  const userByPhone = await groo.getUserByPhone('+1234567890')

  // Get/set app data
  const data = await groo.getUserData('user-id')
  await groo.setUserData('user-id', { plan: 'premium' })
})

Token Management

Programmatically manage API tokens for your application. All token methods support generic types for type-safe appData:

const groo = grooAuth({
  clientId: env.CLIENT_ID,
  clientSecret: env.CLIENT_SECRET,
})

// Define your token data type
interface TokenData {
  environment: 'production' | 'staging'
  permissions: string[]
}

// List all tokens (with typed appData)
const tokens = await groo.getTokens<TokenData>()

// Create a new token (with typed appData)
const { token, secret } = await groo.createToken<TokenData>({
  name: 'CI/CD Pipeline',
  description: 'For automated deployments',
  expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000), // 90 days
  appData: { environment: 'production', permissions: ['deploy'] },
})
console.log('Save this secret:', secret) // Only shown once!

// Get a specific token (with typed appData)
const tokenInfo = await groo.getToken<TokenData>(token.id)

// Update token app data (type-safe)
await groo.setTokenData<TokenData>(token.id, {
  environment: 'staging',
  permissions: ['deploy', 'rollback'],
})

// Get token app data (typed)
const data = await groo.getTokenData<TokenData>(token.id)
// data.environment is typed as 'production' | 'staging'

// Revoke a token
await groo.revokeToken(token.id)

Token Management Methods

| Method | Description | |--------|-------------| | getTokens() | List all API tokens for the application | | getToken(tokenId) | Get a specific token by ID | | createToken(options) | Create a new API token (returns secret once!) | | revokeToken(tokenId) | Revoke/delete a token | | getTokenData(tokenId) | Get token-specific app data | | setTokenData(tokenId, data) | Set token-specific app data |

CreateTokenOptions

interface CreateTokenOptions {
  name: string              // Required: token name
  description?: string      // Optional: description
  expiresAt?: string | Date // Optional: expiration date
  appData?: Record<string, unknown> // Optional: custom data
}

User Object

interface User {
  id: string
  email: string | null
  phone: string | null
  name: string | null
  role: string
}

interface ConsentedUser extends User {
  consent: {
    id: string
    userId: string
    applicationId: string
    consentedAt: string
    lastAccessedAt: string
    revokedAt: string | null
    appData: Record<string, unknown>  // App-specific user data
  }
}

Full Example

// src/index.ts
import { Hono } from 'hono'
import { grooAuth } from '@groo.dev/auth-server'
import { GrooHonoMiddleware } from '@groo.dev/auth-server/hono'

type Env = {
  CLIENT_ID: string
  CLIENT_SECRET: string
  ACCOUNTS_URL: string
}

const hono = new GrooHonoMiddleware<Env>((env) => grooAuth({
  clientId: env.CLIENT_ID,
  clientSecret: env.CLIENT_SECRET,
  baseUrl: env.ACCOUNTS_URL,
}))

const app = new Hono<{ Bindings: Env }>()

// Initialize groo in context
app.use('*', hono.init)

// Mount auth routes for frontend SDK
app.route('/v1', hono.routes)

// Public endpoint
app.get('/v1/health', (c) => {
  return c.json({ status: 'ok' })
})

// Protected endpoint
app.get('/v1/profile', hono.middleware, (c) => {
  const user = c.get('user')
  return c.json({ user })
})

// Admin endpoint - list all consented users
app.get('/v1/admin/users', hono.middleware, async (c) => {
  const user = c.get('user')
  if (user?.role !== 'admin' && user?.role !== 'superadmin') {
    return c.json({ error: 'Forbidden' }, 403)
  }

  const groo = c.get('groo')
  const result = await groo.getUsers()
  return c.json(result)
})

export default app

Cloudflare Workers Configuration

// wrangler.jsonc
{
  "vars": {
    "ACCOUNTS_URL": "https://accounts.groo.dev"
  }
}

// Store secrets securely:
// wrangler secret put CLIENT_ID
// wrangler secret put CLIENT_SECRET

How It Works

Session-Based Authentication

  1. Frontend sends requests with session cookie
  2. Middleware validates session AND checks consent via accounts API
  3. If valid and consented, ConsentedUser (including appData) is added to Hono context
  4. Route handlers access user via c.get('user')

Machine-to-Machine Operations

  1. Use c.get('groo') to access the auth client in route handlers
  2. Client uses Basic auth with clientId and clientSecret
  3. Can list/get users who have consented to the application
  4. Can store and retrieve app-specific data for each user

Related Packages

License

MIT