npm.io
0.2.0 • Published 19h ago

@mks2508/auth-oidc-elysia

Licence
MIT
Version
0.2.0
Deps
4
Size
60 kB
Vulns
0
Weekly
0

@mks2508/auth-oidc-elysia

Pocket ID OIDC plugin for Elysia. PKCE Authorization Code flow, JWKS validation, session JWT (HS256), requireAuth() guard.

Install

bun add @mks2508/auth-oidc-elysia elysia @elysiajs/jwt

Usage

import { Elysia } from 'elysia'
import { createAuthPlugin, requireAuth } from '@mks2508/auth-oidc-elysia'

const app = new Elysia()
  .use(createAuthPlugin({
    issuerUrl: 'https://auth-provider.example.com',
    clientId: 'my-app',
    clientSecret: process.env.OIDC_CLIENT_SECRET,
    redirectUri: 'https://admin.example.com/auth/callback/oidc',
    sessionSecret: process.env.OIDC_SESSION_SECRET!,
    adminSubs: ['<allowed-user-uuid>'],
    adminUiUrl: 'https://admin.example.com',
    cookieDomain: '.example.com',
  }))
  .get('/admin/data', () => ({ secret: 42 }), { beforeHandle: requireAuth() })
  .listen(3000)

Config options

Option Type Required Default Description
issuerUrl string Yes Pocket ID issuer URL
clientId string Yes OIDC client_id
clientSecret string No OIDC client_secret (omit for PKCE-only public clients)
redirectUri string Yes OIDC callback URI registered in Pocket ID
sessionSecret string Yes HS256 secret for session JWT (32+ chars)
adminSubs string[] No [] Allowed Pocket ID user subs. Empty = any authenticated user
adminUiUrl string No / Post-login redirect URL
cookieDomain string No host-only Cookie domain for cross-subdomain sessions
bypass boolean No false Bypass auth entirely (local dev only)
sessionTtl number No 604800 Session JWT TTL in seconds (7 days)
trustedOrigins string[] No [] Extra trusted origins for token-mode return_to validation (e.g. SPA web domains). Merged with loopback CLI defaults + adminUiUrl
cookieSecure boolean No true Set Secure flag on cookies. Set false only for local http dev (no TLS)
audience string | string[] No Expected aud claim value(s) for raw access tokens. When set, tokens whose aud doesn't match are rejected (fail-closed)
allowedClientIds string[] No [] Allowlist of OIDC client_ids permitted to call (derived from aud[0]). When set, unlisted clients are rejected (fail-closed)

Auth routes

Route Method Auth Description
/auth/health GET Public Health check + issuer URL
/auth/status GET Public Returns session info
/auth/login/oidc GET Public Initiate PKCE flow
/auth/callback/oidc GET Public Exchange code, set session
/auth/logout POST Public Clear session cookie

CLI loopback (PKCE only)

For CLI tools (gh-style device auth): pass ?token_mode=true&return_to=http://127.0.0.1:54321/cb to /auth/login/oidc. The callback returns the session token in a URL fragment instead of setting a cookie — safe for local server capture.

GET /auth/login/oidc?token_mode=true&return_to=http://127.0.0.1:54321/cb
→ 302 https://auth-provider.example.com/authorize?...
→ 302 http://127.0.0.1:54321/cb#token=<session_jwt>

M2M (client_credentials) validation

Machine callers (a CI/build runner, another service) authenticate with a raw Authorization: Bearer <access_token> obtained via Pocket ID's client_credentials grant — no session cookie, no PKCE flow. Pocket ID issues these tokens with sub = "client-<client_id>" and aud = [client_id].

Configure audience and/or allowedClientIds to lock down which callers are accepted; both checks are fail-closed and, when configured, reject the same way an invalid token does (requireAuth() → 401):

const app = new Elysia()
  .use(createAuthPlugin({
    issuerUrl: 'https://auth-provider.example.com',
    clientId: 'release-hub',
    redirectUri: 'https://hub.example.com/auth/callback/oidc',
    sessionSecret: process.env.OIDC_SESSION_SECRET!,
    audience: 'release-hub-api',            // reject tokens not issued for this API
    allowedClientIds: ['build-runner-01'],  // only this M2M client may call
  }))
  .post('/ingest/artifact', ({ clientId, isM2M }) => {
    return { received: true, clientId, isM2M };
  }, { beforeHandle: requireAuth() });

IAuthContext exposes two derived fields on requests authenticated via a raw access token (JWKS-verified — not via a session cookie):

  • isM2Mtrue when sub has the Pocket ID M2M shape client-<client_id>
  • clientId — the caller's OIDC client_id, read from the JWT aud claim (aud[0])

Exports

import {
  createAuthPlugin,    // Main plugin factory
  requireAuth,         // beforeHandle guard
  AuthErrorCode,       // Error code enum
  authError,           // Result<never, ResultError> factory
  type IAuthConfig,    // Plugin config interface
  type IAuthContext,   // Request context after middleware
  type IAuthStatusResponse, // /auth/status response shape
  type ISessionPayload,     // Session JWT payload shape
} from '@mks2508/auth-oidc-elysia'

License

MIT

Keywords