This file is generated from the live auth and records-service source files. Do not edit it by hand; regenerate it fromapps/docs-site/withbun run generate.
Generated from: apps/core/auth/src/platform/better-auth/auth.ts, apps/core/auth/src/index.ts, apps/delta/records-service/src/index.ts, apps/delta/records-service/src/middleware/surreal-auth.ts, apps/delta/application-list/src/features/forms-management/forms-api.ts, apps/delta/audits/src/platform/service-clients/api-client.ts, packages/auth/permissions/src/hono.ts, packages/database/migrations/migrations/0123_nela_scoped_jwt_access.surql
What this page is tracing
- The browser-side Better Auth client call that retrieves a JWT for
records-service. - The point where
records-serviceverifies that JWT with auth-service JWKS and Better Auth-backed security rules. - The handoff where the same token becomes a SurrealDB-authenticated connection through
db.authenticate(token). - The optional token-exchange branch for non-
crowndatabases such asnela.
Flow checkpoints
- The browser retrieves a Better Auth JWT with
authClient.token()before it calls protectedrecords-serviceroutes. records-servicemountssurrealAuthMiddlewareon/api/*before route handlers, so auth runs before business logic touches request state.createAuthMiddleware()verifies the JWT with auth-service JWKS and can rehydrate Better Auth session data when route logic needs current active context rather than raw token claims alone.- The middleware can also call back into auth-service for active organization / workspace / team context, so Better Auth remains the source of truth for scope resolution.
- The current
records-servicemiddleware uses the same JWT directly against SurrealDB by creating a request-scoped connection and callingdb.authenticate(token). - If a service needs a non-default database, auth-service can exchange the token first; the live target list currently includes
nela.
Frontend token handoff examples
apps/delta/audits/src/platform/service-clients/api-client.tscallsauthClient.token()and sendsAuthorization: Bearer <jwt>.
Better Auth modules shaping this path
| Module | Why it matters to records-service |
|---|---|
| JWT | Signs the downstream token, exposes JWKS, and defines the Surreal-facing ns / db / ac claims. |
| Two Factor | Adds the second-factor state that surfaces as twoFactorEnabled, which records-service requires before protected access. |
| Email OTP | Drives verification and recovery OTP flows that ultimately feed the emailVerified claim the middleware enforces. |
| Organization | Keeps organization and team membership inside the Better Auth session that downstream services can rehydrate. |
| Workspace | Adds active workspace and team context so downstream services know which scope the user is operating in. |
| Permissions | Feeds the role-template and custom-permission pipeline that becomes the request ability in records-service. |
| Admin Access | Adds privileged admin flows on top of the same Better Auth runtime, even though ordinary forms traffic only consumes the signed JWT output. |
| External Structure | Extends Better Auth with partner and delegated-access context that rides beside the same JWT/session model. |
| Organization Archive | Extends organization lifecycle handling inside auth-service without changing how records-service verifies tokens. |
JWT claims detected in the auth app
The current Better Auth JWT payload includes: ns, db, ac, sub, id, email, role, null, emailVerified, twoFactorEnabled, false, twoFactorMethod, twoFactorMethods, activeWorkspaceId, activeOrganizationId, activeTeamId, orgRole, teamRole.
Those claims matter because the token is not only verified at the HTTP edge; it is also reused by SurrealDB, where ACCESS policies can inspect values such as $token.ns, $token.db, $token.ac, and $token.sub.
Where Better Auth sits relative to records-service
- Better Auth itself lives inside
auth-service;records-servicedoes not run Better Auth directly. - The browser asks Better Auth for a JWT through the client SDK, then forwards that JWT to
records-service. records-servicetrusts only tokens that validate against auth-service JWKS and match the expected issuer/audience.- Crown Better Auth extensions such as workspace, organization, and permissions shape the active scope and permission context that downstream middleware consumes.
@crown/better-auth-permissionsis the bridge that turns the Better Auth token/session world intouserDb,userContext, andabilityon the Hono request.
Source checkpoints
| Stage | Evidence | What it proves |
|---|---|---|
| Browser gets a JWT | apps/delta/application-list/src/features/forms-management/forms-api.ts, apps/delta/audits/src/platform/service-clients/api-client.ts | Frontend code calls authClient.token() and forwards the JWT as a bearer token before it calls records-service. |
| Better Auth signs the token | apps/core/auth/src/platform/better-auth/auth.ts | The current JWT lifetime is 15m and the keypair uses EdDSA / Ed25519. |
records-service guards /api/* | apps/delta/records-service/src/index.ts, apps/delta/records-service/src/middleware/surreal-auth.ts | The middleware requires verified email + two-factor enabled before protected routes run. |
| JWT becomes request context | packages/auth/permissions/src/hono.ts | createAuthMiddleware() verifies JWKS, resolves Better Auth session/context, loads custom roles, and builds the request ability. |
| Same JWT reaches Surreal | packages/auth/permissions/src/hono.ts, apps/delta/records-service/src/middleware/surreal-auth.ts | The shared auth middleware authenticates the underlying Surreal connection with the request token. |
| Alternate DB tokens | apps/core/auth/src/index.ts, packages/database/migrations/migrations/0123_nela_scoped_jwt_access.surql | The token exchange route can mint a database-specific JWT, and the nela access migration shows Surreal validating db = nela and ac = nela. |
Alternate database note
The current records-service configuration points createSurrealAuthMiddleware() at the default crown database, so it uses the incoming Better Auth JWT directly. The token-exchange route still matters because the middleware can request a new JWT before calling db.authenticate() when a service needs a different Surreal database or access scope.
Generation note
- This page is regenerated from the auth app,
records-service, and the shared auth middleware. - If the token shape, security gates, or Surreal authentication path change, the next docs generation run updates this page and its flow diagram automatically.