Auth
Strategy-based authentication with JWT support, token extractors, and the @Public decorator.
@miiajs/auth provides a strategy-based authentication system with built-in JWT and local (username/password) strategies.
Installation
npm install @miiajs/auth
Quick setup
import { Module } from '@miiajs/core'
import { ConfigModule, ConfigService } from '@miiajs/config'
import { JwtModule } from '@miiajs/auth/jwt'
@Module({
imports: [
ConfigModule.configure({ schema: EnvSchema }),
JwtModule.configure((resolve) => ({
secret: resolve(ConfigService).getOrThrow('JWT_SECRET'),
expiresIn: '1h',
})),
],
controllers: [AuthController],
providers: [MyJwtStrategy, MyLocalStrategy],
})
class AppModule {}
Strategies
A strategy is a class that validates incoming requests. Define one with the @Strategy() decorator:
import { Strategy } from '@miiajs/auth'
import type { AuthStrategy } from '@miiajs/auth'
@Strategy('api-key')
class ApiKeyStrategy implements AuthStrategy {
async validate(ctx: RequestContext) {
const key = ctx.req.headers.get('x-api-key')
if (key !== 'my-secret') throw new UnauthorizedException()
return { type: 'api' }
}
}
JWT strategy
Extend JwtStrategy for Bearer token authentication:
import { Strategy } from '@miiajs/auth'
import { JwtStrategy, fromHeader } from '@miiajs/auth/jwt'
import type { JwtPayload } from '@miiajs/auth/jwt'
@Strategy('jwt')
class MyJwtStrategy extends JwtStrategy {
extractToken = fromHeader() // Authorization: Bearer <token>
protected authenticate(payload: JwtPayload, ctx: RequestContext) {
return { id: payload.sub, role: payload.role }
}
}
The authenticate() method is optional — by default it returns the raw JWT payload.
Local strategy
Extend LocalStrategy for username/password authentication:
import { Strategy } from '@miiajs/auth'
import { LocalStrategy } from '@miiajs/auth/local'
@Strategy('local')
class MyLocalStrategy extends LocalStrategy {
protected usernameField = 'email' // default: 'username'
private userService = inject(UserService)
async authenticate(email: string, password: string) {
const user = await this.userService.findByEmail(email)
if (!user || !await verify(user.passwordHash, password)) {
throw new UnauthorizedException('Invalid credentials')
}
return { id: user.id, role: user.role }
}
}
AuthGuard
Use AuthGuard() to protect routes with a strategy:
import { AuthGuard } from '@miiajs/auth'
import { UseGuard } from '@miiajs/core'
@Controller('/users')
@UseGuard(AuthGuard()) // defaults to 'jwt'
class UserController {
@Get('/')
list() { return [] }
@Post('/login')
@UseGuard(AuthGuard('local'))
login(ctx: RequestContext) {
// ctx.user is set by the strategy
return { user: ctx.user }
}
}
@Public
Mark routes as public to skip authentication:
import { Public } from '@miiajs/auth'
@Controller('/posts')
@UseGuard(AuthGuard())
class PostController {
@Get('/')
@Public() // No auth required
list() { return [] }
@Post('/')
create(ctx: RequestContext) {
// Auth required
return ctx.body
}
}
@Public() can also be applied at the class level to make all routes public.
JwtService
Sign and verify JWT tokens:
import { JwtService } from '@miiajs/auth/jwt'
@Injectable()
class AuthService {
private jwt = inject(JwtService)
async login(user: { id: string; role: string }) {
const token = await this.jwt.sign(
{ sub: user.id, role: user.role },
{ expiresIn: '7d' },
)
return { token }
}
async verify(token: string) {
return this.jwt.verify(token)
}
}
Sign options
interface JwtSignOptions {
secret?: string // Override module secret
algorithm?: string // e.g. 'HS256', 'RS256'
expiresIn?: string | number // e.g. '1h', 3600
subject?: string
issuer?: string
audience?: string
}
Verify options
interface JwtVerifyOptions {
secret?: string
algorithms?: string[]
issuer?: string
audience?: string
}
JwtModule configuration
JwtModule.configure({
secret: 'my-secret', // Required for HMAC algorithms
algorithm: 'HS256', // Default
expiresIn: '1h', // Default
issuer: 'my-app', // Optional
audience: 'my-api', // Optional
})
Or with DI factory:
JwtModule.configure((resolve) => ({
secret: resolve(ConfigService).getOrThrow('JWT_SECRET'),
expiresIn: '1h',
}))
Token extractors
Control where the JWT token is read from:
import { fromHeader, fromCookie, fromQuery } from '@miiajs/auth/jwt'
@Strategy('jwt')
class MyJwtStrategy extends JwtStrategy {
// Authorization: Bearer <token> (default)
extractToken = fromHeader()
// Custom header: X-Auth-Token: <token>
extractToken = fromHeader('x-auth-token', '')
// Cookie: access_token=<token>
extractToken = fromCookie('access_token')
// Query string: ?token=<jwt>
extractToken = fromQuery('token')
}
Complete example
// auth.controller.ts
@Controller('/auth')
class AuthController {
private jwt = inject(JwtService)
@Post('/login')
@UseGuard(AuthGuard('local'))
async login(ctx: RequestContext) {
const token = await this.jwt.sign({
sub: ctx.user.id,
email: ctx.user.email,
role: ctx.user.role,
})
return { token }
}
}
// user.controller.ts
@Controller('/users')
@UseGuard(AuthGuard())
class UserController {
private userService = inject(UserService)
@Get('/')
@Public()
findAll() {
return this.userService.findAll()
}
@Get('/:id')
findOne(ctx: RequestContext) {
return this.userService.findById(ctx.params.id)
}
}
Exports
// @miiajs/auth
import { AuthGuard, Strategy, Public, isPublic } from '@miiajs/auth'
// @miiajs/auth/jwt
import { JwtModule, JwtService, JwtStrategy, fromHeader, fromCookie, fromQuery } from '@miiajs/auth/jwt'
// @miiajs/auth/local
import { LocalStrategy } from '@miiajs/auth/local'