# DaloyJS auth docs

DaloyJS does not bundle a user database or login UI. It ships primitives that 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](/docs/auth/architecture). 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.

OAuth2 role Resource Server

Your API's job. DaloyJS is built for this.

Accepts a token that somebody else issued, verifies its signature and claims, and decides whether this caller may touch this resource. Verifying is not the same as issuing: you always own verification, even when a provider owns the login.

Every page in this section wires DaloyJS up as the thing that *checks* a token. The provider owns the accounts, the login screen, the password resets, and the token lifetime. That split keeps the high-blast-radius problems with a team whose full-time job is solving them.

[Auth architecture: where DaloyJS fits in OAuth2 & OpenID Connect](/docs/auth/architecture) explains all three roles and the two deployment shapes we recommend.

## Supported providers

- [AWS Cognito](/docs/auth/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)](/docs/auth/entra-id): 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](/docs/auth/auth0): developer-friendly IdP with universal login, MFA, and rich rule engine. Verify access tokens with `jose` against your tenant's issuer URL.
- [Okta](/docs/auth/okta): workforce identity with custom authorization servers and granular policies. Use the official `@okta/jwt-verifier` for access and ID tokens.
- [Clerk](/docs/auth/clerk): modern, embeddable authentication with user, organization, and billing primitives. Use `@clerk/backend` `authenticateRequest()` to authenticate any `Request`.
- [LoginRadius](/docs/auth/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](/docs/auth/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()`.

**Diagram: One resource server, many identity providers**

- **DaloyJS API (resource server)** (source, your code) - verifies tokens/sessions, gates routes by scope/role
- **AWS Cognito** - aws-jwt-verify
- **Microsoft Entra ID** - jose + OIDC JWKS
- **Auth0** - jose
- **Okta** - @okta/jwt-verifier
- **Clerk** - @clerk/backend
- **LoginRadius** - loginradius-sdk
- **Better Auth** - auth.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 and use getSession for cookie/session auth).

## Runtime compatibility at a glance

| Provider | Node / Bun / Deno | Cloudflare Workers | AWS Lambda |
| --- | --- | --- | --- |
| AWS Cognito (`aws-jwt-verify`) | Yes | Yes (Web Crypto) | Yes |
| Entra ID (`jose`) | Yes | Yes | Yes |
| Auth0 (`jose`) | Yes | Yes | Yes |
| Okta (`@okta/jwt-verifier`) | Yes | No (Node-only) | Yes |
| Clerk (`@clerk/backend`) | Yes | Yes | Yes |
| LoginRadius (`loginradius-sdk`) | Yes (Node-style) | No | Yes |
| Better Auth (`better-auth`) | Yes | Depends on database adapter | Yes |

## 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).

**Diagram: The same three steps on every provider page**

1. **Install the verifier SDK** - pnpm add <provider-sdk>
2. **Register an auth plugin** - decorates ctx with a verifier
3. **Guard routes** - requireAuth(...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 after you 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](/docs/security/resource-authorization) to every user-owned or tenant-owned record.
- Use TLS everywhere. Bearer tokens are plaintext-equivalent. Require HTTPS and set the [`secureHeaders`](/docs/security) middleware (`Strict-Transport-Security`).
- Rate-limit token-issuing routes. Login redirects, token-exchange endpoints, and any introspection passthroughs should go through [`rateLimit`](/docs/security) (or the [Redis store](/docs/security/rate-limit-redis)) 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](/docs/security/csrf) and use `SameSite=Lax` + `Secure` + `HttpOnly` via the built-in [session middleware](/docs/security/session).

---

Source: https://daloyjs.dev/docs/auth