Authentication Middleware
Introduction
Section titled “Introduction”The authentication middleware runs at the HTTP boundary. It extracts a credential from the incoming request, delegates credential validation to the configured authentication service, and writes the resulting identity to the current request context.
Authentication answers who is making the request. It does not decide whether that identity can execute a command, query, or route. Use application authorization policies for access decisions.
The flow separates transport from identity verification:
HTTP request │ ├── Token extractor: reads a credential from headers or cookies │ ├── Authentication service: validates the credential or resolves a session │ ├── ClaimsIdentityMapper: maps claims to Identity │ └── RequestContext: makes the identity available downstreamConfigure Authentication
Section titled “Configure Authentication”Register an authentication provider with addAuth() and enable the HTTP middleware stack with addMiddlewares():
import { XenoAppBuilder } from '@xeno-js/core'import { withSupabase } from '@xeno-js/supabase'import { ordersRoutes } from './order/order.route'
const app = new XenoAppBuilder()
app.addRoutes(ordersRoutes)app.addAuth( withSupabase((options, config) => { options.url = config.getOrThrow('SUPABASE_URL') options.key = config.getOrThrow('SUPABASE_KEY') }),)app.addMiddlewares()
const application = await app.build()ordersRoutes represents the application’s route definitions. addAuth() queues the authentication provider; addMiddlewares() queues the HTTP middleware module. The builder resolves queued modules during build(). Configuring an authentication provider without adding the middleware stack does not authenticate incoming HTTP requests.
The provider supplies an authentication service and may supply a token extractor. Without a provider, addMiddlewares() still installs AuthenticationMiddleware, but Xeno uses NoAuthGateKeeper, which ignores any supplied token and always establishes a guest identity. Do not treat this configuration as authentication for protected endpoints.
Credential Extraction
Section titled “Credential Extraction”The middleware does not parse credentials itself. It calls the configured token extractor with the request headers.
When no custom extractor is registered, Xeno uses BearerTokenExtractor. It checks:
- An
Authorizationheader with theBearerscheme. - The configured authentication cookie, if no bearer token was found.
The default cookie name is sb-access-token. authCookieName in the middleware options changes this fallback when the middleware creates the default extractor. An authentication integration can register its own extractor and cookie handling; in that case, the integration’s configuration controls extraction.
For example, a bearer token is sent as:
Authorization: Bearer <access-token>For Supabase-specific credential and cookie behavior, see Supabase Authentication.
Validate Credentials and Establish Identity
Section titled “Validate Credentials and Establish Identity”When the extractor returns a token, the configured gatekeeper calls the authentication service’s authenticate(token) method. If authentication succeeds, Xeno maps the returned claims to an Identity and updates the request context before continuing the middleware pipeline.
The default ClaimsIdentityMapper maps:
subtoIdentity.userId;tenantIdtoIdentity.tenantId;email,name,roles, andpermissionsto the corresponding identity properties.
The mapper parses user and tenant identifiers as GUIDs. Provide valid GUID strings when the application relies on those identifiers.
When the extractor returns no token, a configured gatekeeper calls getUser() to resolve an existing session. If the service returns no user or fails to resolve one, the gatekeeper uses the guest identity and continues. Thus, the HTTP authentication middleware does not automatically require every request to have an authenticated identity.
The identity is stored in the RequestContext and is available to downstream middleware and application services. In the default Node.js context, AsyncLocalStorage carries request-specific context through asynchronous execution. Authentication services are request-scoped; do not retain them in singleton services.
Authentication Failures
Section titled “Authentication Failures”If a supplied token fails authentication, the middleware:
- Logs a warning containing the request path and authentication error.
- Returns an error response using the authentication error’s status, or
401 Unauthorizedwhen no status is supplied. - Includes the error code, message, request path, and correlation identifiers in the response.
- Stops the pipeline without calling the next middleware.
A missing token is different from an invalid token. When no token is extracted, Xeno attempts session lookup and then falls back to the guest identity; when a supplied token is rejected, the middleware returns an error response.
Require an Authenticated User
Section titled “Require an Authenticated User”AuthenticationMiddleware establishes identity but does not, by itself, reject guest requests. Decide at the application boundary which operations require an authenticated user, tenant, role, or permission.
For example, an authorization policy can require a user identity or a role before executing a command. Keep those access rules in the application authorization pipeline rather than treating successful execution of the HTTP authentication middleware as proof that the caller is authorized.
See Authorization for authorization strategies and policies.
Configure a Custom Authentication Service
Section titled “Configure a Custom Authentication Service”Xeno supports custom providers through addAuth(). Configure an authService and, when needed, a tokenExtractor:
import { XenoAppBuilder } from '@xeno-js/core'
// Implement these application-specific classes against Xeno's auth and extractor contracts.const app = new XenoAppBuilder()
app.addAuth((options) => { options.authService = () => new MyAuthService() options.tokenExtractor = () => new MyTokenExtractor()})
app.addMiddlewares()
const application = await app.build()MyAuthService must implement Xeno’s authentication service contract, and MyTokenExtractor must implement IServiceExtractor<Request['headers'], Optional<string>>. The auth service factory receives the active service scope; Xeno registers the authentication service as scoped. The token extractor is registered as a singleton, so it must not capture request-scoped services.
The authentication service validates a supplied token through authenticate(token). When the extractor returns no token, the gatekeeper calls getUser() to resolve a session. isAuthenticated() is part of the service contract but is not called by the HTTP authentication middleware.
See Authentication Overview for the complete service contract and Custom Authentication for provider implementation details.
Common Issues
Section titled “Common Issues”A route executes without a logged-in user
Section titled “A route executes without a logged-in user”This is expected when no credential or session resolves to an authenticated user. The gatekeeper assigns a guest identity and continues. Add application authorization rules to operations that require an authenticated user.
A valid-looking token returns 401 Unauthorized
Section titled “A valid-looking token returns 401 Unauthorized”Check that the extractor reads the credential from the location used by the client, that the authentication service accepts the token, and that the configured provider and environment values are correct. A custom extractor must return the raw credential expected by the authentication service.
Authentication middleware ignores the supplied token
Section titled “Authentication middleware ignores the supplied token”Check whether addAuth() configured the provider before build(). Without an authentication provider, NoAuthGateKeeper deliberately ignores credentials and always returns a guest identity.
The token is read from the wrong cookie
Section titled “The token is read from the wrong cookie”Check the authentication integration’s token extractor configuration. authCookieName applies to the default bearer-token extractor created by the middleware; a provider that registers its own extractor may use different cookie options.
Authentication succeeds but an operation is denied
Section titled “Authentication succeeds but an operation is denied”Authentication only establishes identity. Inspect the application’s authorization policies, including any user, tenant, role, or permission requirements.
Related Documentation
Section titled “Related Documentation”- Authentication Overview
- Supabase Authentication
- Custom Authentication
- Authorization
- Request Context Middleware
- Middleware Overview
- Security Overview
Support Us
Section titled “Support Us”Xeno.JS is an MIT-licensed open source project. It can grow thanks to the support of these awesome people. If you’d like to join them, please read more at support section
