Skip to content
Development
Command

/forge-oauth-callback

Scaffold a complete OAuth2 callback + init Hono route pair (code exchange, PKCE, KV token storage) for a named provider

From plugin
heymegabyte-claude-skills
2153 skills27 agents53 commands
Install
> /plugin marketplace add heymegabyte/claude-skills

How it fires

How this command gets triggered: by you, by Claude, or both.

  • Fires itselfClaude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/forge-oauth-callback

Context preview

What this command does when you run it.

Scaffold a complete OAuth2 callback + init Hono route pair (code exchange, PKCE, KV token storage) for a named provider

Command definition

forge-oauth-callback.md
description: Scaffold a complete OAuth2 callback + init Hono route pair (code exchange, PKCE, KV token storage) for a named provider
argument-hint: <provider>  # auth0 | okta | cognito | pkce-auth0
allowed-tools: Bash, Read, Write, Edit, Glob

Scaffold a production-ready OAuth2 callback route + a matching `/oauth/start/:provider` init route for the named provider. Reads provider preset from `template/utils/<provider>-token-cache.ts` if present. One command, two routes, one E2E spec.

> **Pattern B — Write each file in a SEPARATE Write tool call. Do not batch into one.**

Supported providers

| Provider | Flow | Notes | |---|---|---| | `auth0` | Authorization Code | Standard; no PKCE unless `pkce-auth0` chosen | | `okta` | Authorization Code | OIDC-compatible; `.well-known/openid-configuration` discovery | | `cognito` | Authorization Code | AWS; `TOKEN_ENDPOINT` from User Pool domain | | `pkce-auth0` | Authorization Code + PKCE | Auth0 with S256 code challenge; no client secret needed |

> **M2M flag:** if user passes `bitwarden` or any provider described as M2M / client-credentials, print a clear warning: > "M2M flows use client_credentials grant — there is no user callback. Use `/oauth/start/:provider` with grant_type=client_credentials to obtain a token server-side, then store it in KV. No callback route needed." Then exit — do not scaffold a callback for M2M.

What gets generated

src/web/routes/
  oauth-callback.ts     ← GET /oauth/callback/:provider — code exchange + token KV storage
  oauth-init.ts         ← GET /oauth/start/:provider — state + PKCE setup + redirect to provider

e2e/oauth/
  <provider>.spec.ts    ← Playwright happy-path E2E

Execution

PROVIDER="${ARGUMENTS%% *}"
echo "Forging OAuth routes for: $PROVIDER"

Step 1 — validate provider arg

VALID="auth0 okta cognito pkce-auth0"
if ! echo "$VALID" | grep -qw "$PROVIDER"; then
  echo "ERROR: unknown provider '$PROVIDER'. Supported: $VALID"
  exit 1
fi

Step 2 — read preset if present

ls template/utils/${PROVIDER}-token-cache.ts 2>/dev/null \
  && cat template/utils/${PROVIDER}-token-cache.ts \
  || echo "No preset found — will synthesize from provider defaults"

Step 3 — detect project root + existing files

find . -maxdepth 3 -name "wrangler.jsonc" -o -name "wrangler.toml" | head -1
ls src/web/routes/ 2>/dev/null | head -20 || echo "no routes dir yet"
ls e2e/oauth/ 2>/dev/null || echo "no oauth E2E dir yet"

Step 4 — write `src/web/routes/oauth-init.ts`

Write as **separate Write call**. File must contain:

// src/web/routes/oauth-init.ts
import { Hono } from 'hono'
import { z } from 'zod'

// Provider configs (fill from preset or synthesize)
const PROVIDERS = {
  'auth0':     { authUrl: process.env.AUTH0_DOMAIN + '/authorize', clientId: process.env.AUTH0_CLIENT_ID, scope: 'openid profile email' },
  'okta':      { authUrl: process.env.OKTA_DOMAIN  + '/oauth2/v1/authorize', clientId: process.env.OKTA_CLIENT_ID, scope: 'openid profile email' },
  'cognito':   { authUrl: process.env.COGNITO_DOMAIN + '/oauth2/authorize', clientId: process.env.COGNITO_CLIENT_ID, scope: 'openid email' },
  'pkce-auth0':{ authUrl: process.env.AUTH0_DOMAIN + '/authorize', clientId: process.env.AUTH0_CLIENT_ID, scope: 'openid profile email' },
} as const

export const oauthInitApp = new Hono()

oauthInitApp.get('/start/:provider', async (c) => {
  const provider = c.req.param('provider') as keyof typeof PROVIDERS
  const config = PROVIDERS[provider]
  if (!config) return c.json({ error: 'unknown_provider' }, 400)

  const returnTo = c.req.query('return_to') ?? '/'
  const state    = crypto.randomUUID()

  // PKCE: generate code_verifier + code_challenge
  let codeVerifier: string | undefined
  let codeChallenge: string | undefined
  if (provider === 'pkce-auth0') {
    codeVerifier = generateCodeVerifier()
    codeChallenge = await generateCodeChallenge(codeVerifier)
  }

  // Persist state + verifier in KV (60s TTL — auth must complete within 1 min)
  const kv = (c.env as any).SESSIONS as KVNamespace
  await kv.put(`oauth:state:${state}`, JSON.stringify({ provider, returnTo, codeVerifier }), { expirationTtl: 600 })

  const params = new URLSearchParams({
    response_type: 'code',
    client_id:     config.clientId ?? '',
    redirect_uri:  new URL('/oauth/callback/' + provider, c.req.url).toString(),
    scope:         config.scope,
    state,
    ...(codeChallenge ? { code_challenge: codeChallenge, code_challenge_method: 'S256' } : {}),
  })

  return c.redirect(`${config.authUrl}?${params.toString()}`)
})

// PKCE helpers
function generateCodeVerifier(): string {
  const buf = new Uint8Array(48)
  crypto.getRandomValues(buf)
  return btoa(String.fromCharCode(...buf)).replace(/\+/g,'-').replace(/\//g,'_').replace(/=/g,'')
}

async function generateCodeChallenge(verifier: string): Promise<string> {
  const data   = new TextEncoder().encode(verifier)
  const digest = await crypto.subtle.digest('SHA-256', data)
  return btoa(String.fromCharCode(...new Uint8Array(digest))).replace(/\+/g,'-').replace(/\//g,'_').replace(/=/g,'')
}

Adapt `PROVIDERS` map from the preset if one was found in Step 2. Preserve all keys and import paths that already exist in the project.

Step 5 — write `src/web/routes/oauth-callback.ts`

Write as **separate Write call**. File must contain:

// src/web/routes/oauth-callback.ts
import { Hono } from 'hono'
import { z } from 'zod'

const CallbackQuerySchema = z.object({
  code:  z.string().min(1),
  state: z.string().uuid(),
})

export const oauthCallbackApp = new Hono()

oauthCallbackApp.get('/callback/:provider', async (c) => {
  const provider = c.req.param('provider')
  const kv = (c.env as any).SESSIONS as KVNamespace

  // Parse + validate query params
  const parsed = CallbackQuerySchema.safeParse({
    code:  c.req.query('code'),
    state: c.req.query('state'),
  })
  if (!parsed.success)
Read more
Ships withheymegabyte-claude-skills

14-category autonomous product-building OS for 32+ AI coding tools. One-line prompts → deployed products.

Get the whole plugin

Other commands on heymegabyte-claude-skills.