Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.deep.space/llms.txt

Use this file to discover all available pages before exploring further.

import {
  verifyJwt, decodeJwtPayload,
  createDeepSpaceAuth,
  verifyInternalSignature, buildInternalPayload,
  signInternalPayload, computeHmacHex, timingSafeEqualHex,
  DEFAULT_MAX_SKEW_MS,
} from 'deepspace/worker'

import type {
  JwtVerifierConfig, VerifyOutcome, VerifyResult, VerifiedAuth, JwtClaims,
  TokenDebugInfo,
  InternalSignature,
  DeepSpaceAuth, DeepSpaceAuthConfig,
} from 'deepspace/worker'

verifyJwt(config, token)

Verifies a JWT against a public key. Does not throw - returns a { result, error?, debug? } envelope.
function verifyJwt(
  config: JwtVerifierConfig,
  token: string | null | undefined,
): Promise<VerifyOutcome>

interface JwtVerifierConfig {
  publicKey: string                 // PEM-encoded ES256; env.AUTH_JWT_PUBLIC_KEY
  issuer: string                    // env.AUTH_JWT_ISSUER
  audience?: string | string[]      // single value or list
  authorizedParties?: string[]      // azp patterns; supports "*" wildcards
  clockSkewMs?: number              // default 5000
}

interface VerifyOutcome {
  result: VerifyResult | null       // null on failure
  debug?: TokenDebugInfo            // unverified claims, for logging
  error?: unknown                   // underlying error (not stringified)
}

interface VerifyResult extends VerifiedAuth {}

interface VerifiedAuth {
  userId: string                    // claims.sub, surfaced for convenience
  claims: JwtClaims                 // full verified payload
}

interface JwtClaims {
  sub: string
  iss?: string
  aud?: string | string[]
  azp?: string
  exp?: number
  iat?: number
  name?: string
  email?: string
  image?: string
  [key: string]: unknown
}
VerifyResult is not a flat JWT-claims object - it’s a { userId, claims } envelope. Read result.userId for the subject, and result.claims.email / result.claims.name / result.claims.image for the optional profile fields:
app.get('/api/me', async (c) => {
  const auth = c.req.header('Authorization') ?? ''
  const token = auth.replace(/^Bearer\s+/i, '')

  const { result } = await verifyJwt({
    publicKey: c.env.AUTH_JWT_PUBLIC_KEY,
    issuer:    c.env.AUTH_JWT_ISSUER,
  }, token)

  if (!result) return c.json({ error: 'unauthorized' }, 401)
  return c.json({
    userId: result.userId,
    email: result.claims.email,
  })
})

decodeJwtPayload(token)

Base64url-decode the JWT payload without verification. Useful for inspecting iss / aud / azp / exp for logging or debugging where verification has already happened upstream.
function decodeJwtPayload(token: string | null | undefined): TokenDebugInfo | undefined

interface TokenDebugInfo {
  iss?: string | null
  aud?: string | string[] | null
  azp?: string | null
  exp?: number | null
  iat?: number | null
}
Returns undefined if the token is missing or malformed.
Never use as a substitute for verifyJwt on the trust boundary. Decoded but unverified claims can be spoofed by anyone, and TokenDebugInfo deliberately omits sub to discourage that.

HMAC primitives - internal signing

For platform → app internal calls (and cron’s ctx.integrations.call, which signs requests with INTERNAL_STORAGE_HMAC_SECRET). All of these are async and use crypto.subtle on the Workers runtime (with a Node fallback for testing).
function buildInternalPayload(body: unknown): string

function signInternalPayload(input: {
  secret: string
  payload: string
  timestamp?: string                // defaults to Date.now().toString()
}): Promise<InternalSignature>

function verifyInternalSignature(input: {
  secret: string | undefined
  timestamp: string | null | undefined
  signature: string | null | undefined
  payload: string
  maxSkewMs?: number                // defaults to DEFAULT_MAX_SKEW_MS
}): Promise<boolean>

function computeHmacHex(secret: string, data: string): Promise<string>
function timingSafeEqualHex(a: string, b: string): Promise<boolean>

interface InternalSignature {
  timestamp: string
  signature: string
}

const DEFAULT_MAX_SKEW_MS: number   // 5 * 60_000 - verification window
buildInternalPayload returns a plain string (JSON-stringified body, or the input string passed through). The timestamp lives on the signature object returned by signInternalPayload, not on the payload. These are exported so apps can verify inbound internal calls (e.g., custom webhook endpoints from the platform) using the same HMAC contract the rest of the SDK uses. Most apps never call these directly.

createDeepSpaceAuth(config)

Construct a Better Auth instance pre-wired for DeepSpace conventions (cookie names, JWT issuance, plugin set). The scaffold doesn’t build its own auth surface - it proxies to the platform auth-worker - so you only reach for this when standing up a custom auth-worker variant.
function createDeepSpaceAuth(config: DeepSpaceAuthConfig): DeepSpaceAuth

interface DeepSpaceAuthConfig {
  /** D1 database binding */
  database: D1Database
  /** Base URL for the auth worker (e.g. "https://auth.deep.space") */
  baseURL: string
  /** Secret for session signing */
  secret: string
  /** Google OAuth credentials (optional) */
  google?: { clientId: string; clientSecret: string }
  /** GitHub OAuth credentials (optional) */
  github?: { clientId: string; clientSecret: string }
  /** Enable email/password authentication (default: true) */
  emailAndPassword?: boolean
  /** Trusted origins for CORS */
  trustedOrigins?: string[]
}

type DeepSpaceAuth = ReturnType<typeof createDeepSpaceAuth>
DeepSpaceAuth is the better-auth Auth instance with the DeepSpace defaults (organization + twoFactor plugins, *.deep.space / *.app.space / localhost:* trusted origins) applied. There are no privateKey / publicKey / issuer fields on the config - JWT signing keys live in the auth-worker’s environment, not in this object.

See also