Files
supabase__server/docs/api-reference.md
T
Ismail Pelaseyed 0251690a7f fix!: reject invalid JWTs immediately instead of falling through to next auth mode (#35)
* fix: reject invalid JWTs immediately instead of falling through to next auth mode

Introduce an INVALID sentinel so tryMode can distinguish "credential
present but failed" from "credential absent." The main loop now short-
circuits on INVALID instead of silently trying the next allowed mode.

Also covers the case where a JWT verifies cryptographically but has no
sub claim (or sub isn't a string) -- previously returned null (fallthrough),
now returns INVALID (reject).

BREAKING CHANGE: when multiple auth modes are allowed, a present-but-invalid
JWT is now rejected with InvalidCredentialsError instead of falling through
to the next mode. Clients that previously relied on silent fallthrough
(e.g., stale token + valid apikey) must now either omit the Authorization
header or refresh the token.

* docs: clarify invalid-JWT no-fallthrough semantics

Align documentation with the behavior introduced in the fix!: commit on
this branch. Make clear across user-facing docs, TSDoc, and SKILL that:

- A mode is "tried" only when its credential is actually present, so a
  request with no Authorization header still falls through.
- A JWT that is present but fails verification (malformed, expired, wrong
  signature, missing sub) rejects with InvalidCredentialsError — it does
  not silently fall through to another allowed mode.

Touches README, docs/auth-modes, docs/security, docs/error-handling,
docs/api-reference, skills/supabase-server/SKILL, and TSDoc on the Allow
type, WithSupabaseConfig.allow, and verifyCredentials.

---------

Co-authored-by: Tomas Pozo <tomaspozogarzon@gmail.com>
2026-04-22 19:22:48 -05:00

7.9 KiB

API Reference

Complete reference for every export, organized by entry point.


@supabase/server

withSupabase

function withSupabase<Database = unknown>(
  config: WithSupabaseConfig,
  handler: (req: Request, ctx: SupabaseContext<Database>) => Promise<Response>,
): (req: Request) => Promise<Response>

Wraps a fetch handler with auth, CORS, and client creation. Returns a (req: Request) => Promise<Response> function suitable for export default { fetch }.

  • Handles OPTIONS preflight when CORS is enabled
  • Verifies credentials per config.allow
  • Returns JSON error response on auth failure
  • Adds CORS headers to all responses

createSupabaseContext

function createSupabaseContext<Database = unknown>(
  request: Request,
  options?: WithSupabaseConfig,
): Promise<
  | { data: SupabaseContext<Database>; error: null }
  | { data: null; error: AuthError }
>

Creates a SupabaseContext from a request. Returns a result tuple. The cors option is ignored.

Defaults to allow: 'user' when options is omitted.


@supabase/server/core

verifyAuth

function verifyAuth(
  request: Request,
  options: { allow: AllowWithKey | AllowWithKey[]; env?: Partial<SupabaseEnv> },
): Promise<{ data: AuthResult; error: null } | { data: null; error: AuthError }>

Extracts credentials from a request and verifies them. Convenience wrapper over extractCredentials + verifyCredentials.

verifyCredentials

function verifyCredentials(
  credentials: Credentials,
  options: { allow: AllowWithKey | AllowWithKey[]; env?: Partial<SupabaseEnv> },
): Promise<{ data: AuthResult; error: null } | { data: null; error: AuthError }>

Verifies pre-extracted credentials against allowed auth modes. Tries each mode in order — first match wins.

extractCredentials

function extractCredentials(request: Request): Credentials

Reads Authorization: Bearer <token> and apikey headers from a request. Pure extraction, no validation. Synchronous.

resolveEnv

function resolveEnv(
  overrides?: Partial<SupabaseEnv>,
): { data: SupabaseEnv; error: null } | { data: null; error: EnvError }

Resolves Supabase environment configuration from runtime variables. SUPABASE_URL is the only hard requirement.

createContextClient

function createContextClient<Database = unknown>(
  options?: CreateContextClientOptions,
): SupabaseClient<Database>

Creates a user-scoped Supabase client. RLS applies. Throws EnvError if URL or publishable key is missing.

Configured with:

  • Publishable key (named or default) as apikey header
  • User's JWT as Authorization: Bearer header (when auth.token is provided)
  • persistSession: false, autoRefreshToken: false, detectSessionInUrl: false

createAdminClient

function createAdminClient<Database = unknown>(
  options?: CreateAdminClientOptions,
): SupabaseClient<Database>

Creates an admin Supabase client that bypasses RLS. Throws EnvError if URL or secret key is missing.


@supabase/server/adapters/hono

withSupabase (Hono)

function withSupabase(
  config?: Omit<WithSupabaseConfig, 'cors'>,
): MiddlewareHandler

Hono middleware. Sets c.var.supabaseContext on the Hono context. Throws HTTPException on auth failure with cause: AuthError.

Skips if c.var.supabaseContext is already set (enables route-level overrides).

Defaults to allow: 'user' when config is omitted.


Types

Allow

type Allow = 'always' | 'public' | 'secret' | 'user'

AllowWithKey

type AllowWithKey = Allow | `public:${string}` | `secret:${string}`

Extended auth mode with named key support. Examples: 'public:web', 'secret:*', 'secret:internal'.

SupabaseContext<Database>

interface SupabaseContext<Database = unknown> {
  supabase: SupabaseClient<Database>
  supabaseAdmin: SupabaseClient<Database>
  userClaims: UserClaims | null
  claims: JWTClaims | null
  authType: Allow
}

WithSupabaseConfig

interface WithSupabaseConfig {
  allow?: AllowWithKey | AllowWithKey[] // default: 'user'
  env?: Partial<SupabaseEnv>
  cors?: boolean | Record<string, string> // default: true
  supabaseOptions?: SupabaseClientOptions<string>
}

SupabaseEnv

interface SupabaseEnv {
  url: string
  publishableKeys: Record<string, string>
  secretKeys: Record<string, string>
  jwks: JsonWebKeySet | null
}

Credentials

interface Credentials {
  token: string | null
  apikey: string | null
}

AuthResult

interface AuthResult {
  authType: Allow
  token: string | null
  userClaims: UserClaims | null
  claims: JWTClaims | null
  keyName?: string | null
}

JWTClaims

interface JWTClaims {
  sub: string
  iss?: string
  aud?: string | string[]
  exp?: number
  iat?: number
  role?: string
  email?: string
  app_metadata?: Record<string, unknown>
  user_metadata?: Record<string, unknown>
  [key: string]: unknown
}

UserClaims

interface UserClaims {
  id: string
  role?: string
  email?: string
  appMetadata?: Record<string, unknown>
  userMetadata?: Record<string, unknown>
}

ClientAuth

interface ClientAuth {
  token?: string | null
  keyName?: string | null
}

CreateContextClientOptions

interface CreateContextClientOptions {
  auth?: ClientAuth
  env?: Partial<SupabaseEnv>
  supabaseOptions?: SupabaseClientOptions<string>
}

CreateAdminClientOptions

interface CreateAdminClientOptions {
  auth?: Pick<ClientAuth, 'keyName'>
  env?: Partial<SupabaseEnv>
  supabaseOptions?: SupabaseClientOptions<string>
}

JsonWebKeySet

interface JsonWebKeySet {
  keys: JsonWebKey[]
}

Error Classes

EnvError

class EnvError extends Error {
  readonly status: 500
  readonly code: string
}

AuthError

class AuthError extends Error {
  readonly status: number // 401 or 500
  readonly code: string
}

Error Code Constants

Constant Value Class Meaning
EnvGenericError 'ENV_ERROR' EnvError Generic environment error
MissingSupabaseURLError 'MISSING_SUPABASE_URL' EnvError SUPABASE_URL not set
MissingPublishableKeyError 'MISSING_PUBLISHABLE_KEY' EnvError Named publishable key not found
MissingDefaultPublishableKeyError 'MISSING_DEFAULT_PUBLISHABLE_KEY' EnvError No default publishable key
MissingSecretKeyError 'MISSING_SECRET_KEY' EnvError Named secret key not found
MissingDefaultSecretKeyError 'MISSING_DEFAULT_SECRET_KEY' EnvError No default secret key
AuthGenericError 'AUTH_ERROR' AuthError Generic auth error
InvalidCredentialsError 'INVALID_CREDENTIALS' AuthError No credential matched, or JWT failed verification
CreateSupabaseClientError 'CREATE_SUPABASE_CLIENT_ERROR' AuthError Client creation failed after auth

Errors Factory Map

const Errors: {
  [MissingSupabaseURLError]: () => EnvError
  [MissingPublishableKeyError]: (name: string) => EnvError
  [MissingDefaultPublishableKeyError]: () => EnvError
  [MissingSecretKeyError]: (name: string) => EnvError
  [MissingDefaultSecretKeyError]: () => EnvError
  [InvalidCredentialsError]: () => AuthError
  [CreateSupabaseClientError]: () => AuthError
}

Keyed by error code constant. Each entry returns a pre-configured error instance.