Auth Wiring
Recipes are working wiring examples with the sharp edges annotated. This page and the
auth-wiringrecipe served to coding agents by the MCP server share one source (@modularityjs/manifest), so they never drift apart.
The auth system has two pools you must not confuse:
AuthResolversPool—token → AuthIdentity. Populated by driver modules (AuthJwtModule.forRoot({...})auto-registersJwtAuthServicehere). Used byAuthService.resolve(token).HttpAuthenticatorsPool—HttpRequest → AuthIdentity. Populated by HTTP-bridge packages.HttpAuthModule(the bridge) ships no defaults: it iterates the pool ononRequestand attaches the first match torequest.auth. If you load onlyHttpAuthModuleand contribute nothing,request.authis alwaysundefined.
For Bearer JWT, the ready-made bridge HttpAuthJwtModule reads Authorization: Bearer <token> and delegates to JwtAuthService. For anything else (custom header, query token, app-specific scheme), contribute an HttpAuthenticator yourself.
Minimum wiring (JWT-protected /me, custom authenticator shown for clarity — HttpAuthJwtModule does the same out of the box):
import { AuthIdentity, AuthModule, AuthService } from '@modularityjs/auth';
import { AuthJwtModule, JwtAuthService } from '@modularityjs/auth-jwt';
import { Inject, Injectable } from '@modularityjs/di';
import {
Body,
Controller,
Get,
HttpControllersPool,
HttpModule,
HttpRequest,
Post,
} from '@modularityjs/http';
import {
Auth,
HttpAuthenticator,
HttpAuthenticatorsPool,
HttpAuthModule,
RequireAuth,
} from '@modularityjs/http-auth';
import { HttpFastifyModule } from '@modularityjs/http-fastify';
import { Module } from '@modularityjs/modularity';
@Injectable()
class BearerAuthenticator implements HttpAuthenticator {
constructor(@Inject(AuthService) private readonly auth: AuthService) {}
async authenticate(request: HttpRequest): Promise<AuthIdentity | undefined> {
const header = request.headers['authorization'];
if (typeof header !== 'string' || !header.startsWith('Bearer '))
return undefined;
return this.auth.resolve(header.slice(7));
}
}
@Controller('/auth')
class LoginController {
constructor(@Inject(JwtAuthService) private readonly jwt: JwtAuthService) {}
@Post('/login')
async login(@Body() body: { name: string }) {
const token = await this.jwt.sign({
id: crypto.randomUUID(),
attributes: { name: body.name },
});
return { token };
}
}
@Controller('/me')
class MeController {
@Get()
@RequireAuth() // 401 before the handler runs if request.auth is missing
me(@Auth() identity: AuthIdentity) {
return identity;
}
}
@Module({
name: 'app',
imports: [HttpModule, AuthModule, HttpAuthModule],
providers: [BearerAuthenticator, LoginController, MeController],
pools: [
{ pool: HttpControllersPool, key: 'login', useClass: LoginController },
{ pool: HttpControllersPool, key: 'me', useClass: MeController },
{
pool: HttpAuthenticatorsPool,
key: 'bearer',
useClass: BearerAuthenticator,
},
],
})
class AppModule {}
// modules: [
// ModularityModule,
// HttpModule.forRoot({ port: 3000 }),
// HttpFastifyModule,
// AuthModule,
// AuthJwtModule.forRoot({ secret: process.env.JWT_SECRET!, issuer: 'my-app', audience: 'my-app' }),
// HttpAuthModule,
// AppModule,
// ]@Auth() by itself returns undefined when authentication is absent or invalid — it does not auto-reject. Pair it with @RequireAuth() (class- or method-level, from @modularityjs/http-auth) so the adapter returns 401 before your handler runs. For routes that also need a specific permission, use @RequirePermission() from @modularityjs/http-authz — it implies @RequireAuth() (401 unauthenticated, 403 unauthorized).