Auth
@stratal/framework integrates with Better Auth to provide authentication, session management, and a request-scoped AuthContext for accessing the authenticated user throughout your application.
Install dependencies
Section titled “Install dependencies”yarn add @stratal/frameworkConfigure AuthModule
Section titled “Configure AuthModule”Use AuthModule.forRootAsync() to configure authentication with async options:
import { Module } from 'stratal/module'import { DI_TOKENS } from 'stratal/di'import { CONFIG_TOKENS, type ConfigService } from 'stratal/config'import { AuthModule } from '@stratal/framework/auth'
@Module({ imports: [ AuthModule.forRootAsync({ inject: [DI_TOKENS.Database, CONFIG_TOKENS.ConfigService], useFactory: (db, config: ConfigService) => ({ database: db, secret: config.get('AUTH_SECRET'), baseURL: config.get('AUTH_BASE_URL'), // ... other Better Auth options }), }), ],})export class AppModule {}The factory receives resolved dependencies and returns a Better Auth options object. Refer to the Better Auth documentation for all available options.
Auth controller
Section titled “Auth controller”Create a controller to proxy all authentication requests to Better Auth’s handler:
import { Controller, type IController, type RouterContext } from 'stratal/router'import { inject } from 'stratal/di'import { AUTH_SERVICE, type AuthService } from '@stratal/framework/auth'
@Controller('/api/auth')export class AuthController implements IController { constructor(@inject(AUTH_SERVICE) private readonly authService: AuthService) {}
async handle(ctx: RouterContext) { return this.authService.auth.handler(ctx.c.req.raw) }}This controller catches all requests under /api/auth/* and forwards them to Better Auth, which handles sign-up, sign-in, session management, and all other auth endpoints.
AuthContext
Section titled “AuthContext”AuthContext is a request-scoped service that holds the authenticated user’s information for the current request. Inject it using DI_TOKENS.AuthContext:
import { Transient, inject, DI_TOKENS } from 'stratal/di'import { AuthContext } from '@stratal/framework/context'
@Transient()export class ProfileService { constructor( @inject(DI_TOKENS.AuthContext) private readonly auth: AuthContext, ) {}
async getProfile() { const user = this.auth.requireUser() return this.usersRepository.findById(user.id) }}AuthContext API
Section titled “AuthContext API”| Method | Return type | Description |
|---|---|---|
isAuthenticated() | boolean | Whether the current request has a valid session |
getUser() | AuthUser | undefined | The authenticated user, or undefined |
requireUser() | AuthUser | The authenticated user. Throws UserNotAuthenticatedError if not authenticated |
getUserId() | string | undefined | The authenticated user’s ID, or undefined |
requireUserId() | string | The authenticated user’s ID. Throws if not authenticated |
getRole() | string | undefined | Raw role string from user.role (comma-separated for multiple roles) |
getRoles() | string[] | Roles parsed into an array. Returns [] when the user has no role |
getAuthInfo() | AuthInfo | The full authentication context object. Throws ContextNotInitializedError if not initialised |
clearAuthContext() | void | Clears the authentication state |
Augmenting AuthUser
Section titled “Augmenting AuthUser”AuthUser extends Better Auth’s base user with name made optional. Augment it via TypeScript module declaration to match whatever additionalFields or plugins your Better Auth config returns:
declare module '@stratal/framework/context' { interface AuthUser { firstName: string lastName: string role: string }}Once augmented, auth.requireUser() and auth.getUser() return the typed shape, and auth.getRole() is typed as string.
AuthService
Section titled “AuthService”The AuthService provides access to the underlying Better Auth instance for advanced operations like session management:
import { Transient, inject } from 'stratal/di'import { AUTH_SERVICE, AuthService } from '@stratal/framework/auth'
@Transient()export class SessionService { constructor( @inject(AUTH_SERVICE) private readonly authService: AuthService, ) {}
get auth() { return this.authService.auth }}The auth property returns the configured Better Auth instance with full access to its API.
Middleware pipeline
Section titled “Middleware pipeline”The AuthModule automatically registers two middleware components:
- AuthContextMiddleware — Creates an
AuthContextinstance in the request-scoped container for every request. - SessionVerificationMiddleware — Verifies the session token from the request and populates
AuthContextwith the authenticated user’s information.
These middleware run before your controller methods, so AuthContext is always available and populated when your code executes.
Next steps
Section titled “Next steps”- Auth Guard for protecting routes with authentication checks.
- Access Control for role-based permissions on top of auth.
- Database for configuring the database used by Better Auth.