Skip to content

Search docs

Jump between documentation pages.

Browse docs

Authentication & authorization

DaloyJS doesn't bundle a user database or login UI, instead, it ships primitives that make it easy to plug in a hosted identity provider (IdP). Your API receives a bearer token, verifies it with the provider's JWKS or SDK, and gates routes by scope, role, or organization. The pages in this section show how to wire up the seven most common IdPs.

New to this? Start with Auth architecture: where DaloyJS fits in OAuth2 & OpenID Connect. It explains why DaloyJS is a resource server (it verifies tokens, it does not issue them), how that compares to .NET and Duende IdentityServer, whether you actually need Auth0/Okta/Clerk or can self-host an open-source IdP, and the two architectures we recommend.

Supported providers

  • AWS Cognito: pay-as-you-go user pools with hosted sign-in. Use aws-jwt-verify to verify access and ID tokens with zero runtime dependencies; runs on Node, edge, and Lambda.
  • Microsoft Entra ID (MSAL): enterprise SSO for Microsoft 365 / Azure AD users. Verify tokens with the OIDC JWKS using jose; acquire downstream tokens with @azure/msal-node when needed.
  • Auth0: developer-friendly IdP with universal login, MFA, and rich rule engine. Verify access tokens with joseagainst your tenant's issuer URL.
  • Okta: workforce identity with custom authorization servers and granular policies. Use the official @okta/jwt-verifier for access and ID tokens.
  • Clerk: modern, embeddable authentication with user, organization, and billing primitives. Use @clerk/backend authenticateRequest() to authenticate any Request.
  • LoginRadius: customer identity, social login, registration, and profile APIs. Use loginradius-sdk to validate LoginRadius access tokens and load profiles from a Node-style DaloyJS runtime.
  • Better Auth: self-hosted authentication for email/password, OAuth, sessions, and plugins. Mount its standard Request → Response handler under DaloyJS and guard API routes with auth.api.getSession().
One resource server, many identity providers
your codeDaloyJS API (resource server)verifies tokens/sessions, gates routes by scope/role
AWS Cognitoaws-jwt-verify
Microsoft Entra IDjose + OIDC JWKS
Auth0jose
Okta@okta/jwt-verifier
Clerk@clerk/backend
LoginRadiusloginradius-sdk
Better Authauth.handler + getSession
DaloyJS stays the resource server in every case. Most providers swap a verifier SDK behind the same interface; Better Auth is the exception (mount its handler + use getSession for cookie/session auth).

Runtime compatibility at a glance

ProviderNode / Bun / DenoCloudflare WorkersAWS Lambda
AWS Cognito (aws-jwt-verify)YesYes (Web Crypto)Yes
Entra ID (jose)YesYesYes
Auth0 (jose)YesYesYes
Okta (@okta/jwt-verifier)YesNo (Node-only)Yes
Clerk (@clerk/backend)YesYesYes
LoginRadius (loginradius-sdk)Yes (Node-style)NoYes
Better Auth (better-auth)YesDepends on database adapterYes

Common pattern

Each provider page implements the same three steps: install the verifier SDK, register a DaloyJS plugin that decorates the request context with an auth object, then guard routes with a small preBody hook (or beforeHandle for body-aware guards) that requires a token (and optional scopes).

The same three steps on every provider page
  1. 01Install the verifier SDKpnpm add <provider-sdk>
  2. 02Register an auth plugindecorates ctx with a verifier
  3. 03Guard routesrequireAuth(...scopes)
Most provider pages follow this shape (verifier + plugin + guard). Better Auth differs: it requires mounting its handler for auth routes and uses getSession for protection instead of a bearer verifier.
ts
// src/plugins/auth.ts
import {
  ForbiddenError,
  UnauthorizedError,
  type App,
  type Hooks,
} from "@daloyjs/core";

export interface Principal {
  sub: string;
  scopes?: string[];
  claims: Record<string, unknown>;
}

export interface TokenVerifier {
  verify(token: string): Promise<Principal>;
}

export function authPlugin(verifier: TokenVerifier) {
  return {
    name: "auth",
    register(app: App) {
      app.decorate("verifier", verifier);
    },
  };
}

export function requireAuth(...requiredScopes: string[]): Hooks {
  return {
    preBody: async (ctx) => {
      const header = ctx.request.headers.get("authorization") ?? "";
      const [scheme, token] = header.split(" ");
      if (scheme?.toLowerCase() !== "bearer" || !token) {
        throw new UnauthorizedError("Missing bearer token");
      }

      let principal: Principal;
      try {
        principal = await ctx.state.verifier.verify(token);
      } catch {
        throw new UnauthorizedError("Invalid or expired token");
      }

      const scopes = principal.scopes ?? [];
      if (requiredScopes.some((scope) => !scopes.includes(scope))) {
        throw new ForbiddenError("Insufficient scope");
      }

      ctx.state.principal = principal;
      // continue (no return value)
    },
  };
}

declare module "@daloyjs/core" {
  interface AppState {
    verifier: TokenVerifier;
    principal?: Principal;
  }
}

Each provider page implements TokenVerifier with the official SDK so the rest of your application stays IdP-agnostic.

Security checklist

  • Always verify the signature.Never trust an unverified JWT, decode-only utilities are for debugging. Use the provider's JWKS endpoint with key caching and automatic rotation (every SDK on the following pages handles this).
  • Check iss and aud. Pin the expected issuer URL and audience/client ID. A correct signature on the wrong audience is still a token confusion attack.
  • Authorize, don't just authenticate. A valid token only proves the caller is who they say they are. Enforce scopes, roles, or organization membership for every privileged action, then apply resource authorization to every user-owned or tenant-owned record.
  • Use TLS everywhere. Bearer tokens are plaintext-equivalent. Require HTTPS and set the secureHeaders middleware (Strict-Transport-Security).
  • Rate-limit token-issuing routes. Login redirects, token-exchange endpoints, and any introspection passthroughs should go through rateLimit (or the Redis store) so abuse can't drive cost or lock out users.
  • Protect cookies and CSRF. If you also use session cookies (for an admin panel, say), enable CSRF and use SameSite=Lax + Secure + HttpOnly via the built-in session middleware.