Skip to main content

Guards

Guards implement authorization logic that runs before endpoint handlers. They control access to your API endpoints.

What are Guards?​

Guards are classes that implement the CanActivate interface. They receive the execution context and return a boolean indicating whether the request should proceed.

Key characteristics:

  • Run before the endpoint handler executes
  • Can access request data (headers, params, body)
  • Return true to allow access, false to deny
  • Can throw exceptions for specific error responses
  • Can attach data to the request for use in handlers

Creating a Guard​

Implement the CanActivate interface:

import { Injectable, CanActivate, AbstractExecutionContext } from '@navios/core'

@Injectable()
class AuthGuard implements CanActivate {
async canActivate(context: AbstractExecutionContext): Promise<boolean> {
const request = context.getRequest()
const token = request.headers.authorization

if (!token) {
return false // Returns 403 Forbidden
}

return true
}
}

Applying Guards​

Module-Level Guards​

Apply guards to all endpoints in a module:

@Module({
controllers: [UserController, ProfileController],
guards: [AuthGuard],
})
class UserModule {}

Endpoint-Level Guards​

Apply guards to specific endpoints using @UseGuards():

import { UseGuards } from '@navios/core'

@Controller()
class UserController {
@Endpoint(getUser)
async getUser(params: EndpointParams<typeof getUser>) {
// Public endpoint - no guard
}

@Endpoint(deleteUser)
@UseGuards(AdminGuard)
async deleteUser(params: EndpointParams<typeof deleteUser>) {
// Protected by AdminGuard
}
}

Multiple Guards​

Apply multiple guards - all must pass:

@Module({
controllers: [AdminController],
guards: [AuthGuard, AdminGuard],
})
class AdminModule {}

// Or on endpoint
@Endpoint(sensitiveAction)
@UseGuards(AuthGuard, AdminGuard)
async sensitiveAction() {}

Guard Execution Order​

  1. Module guards run first (in array order)
  2. Endpoint guards run second (in array order)
  3. If any guard returns false or throws, the request is rejected

Accessing Request Data​

Use the execution context to access request information:

@Injectable()
class AuthGuard implements CanActivate {
async canActivate(context: AbstractExecutionContext): Promise<boolean> {
const request = context.getRequest()

// Access headers
const authHeader = request.headers.authorization

// Access URL parameters
const userId = request.params.userId

// Access query parameters
const token = request.query.token

// Access body
const body = request.body

return true
}
}

Throwing Exceptions​

Throw HTTP exceptions for specific error responses:

import { UnauthorizedException, ForbiddenException } from '@navios/core'

@Injectable()
class AuthGuard implements CanActivate {
async canActivate(context: AbstractExecutionContext): Promise<boolean> {
const request = context.getRequest()
const authHeader = request.headers.authorization

if (!authHeader) {
throw new UnauthorizedException('Missing authorization header')
}

if (!authHeader.startsWith('Bearer ')) {
throw new UnauthorizedException('Invalid authorization format')
}

return true
}
}

Attaching Data to Request​

Guards often attach data for use in handlers:

@Injectable()
class AuthGuard implements CanActivate {
private userService = inject(UserService)

async canActivate(context: AbstractExecutionContext): Promise<boolean> {
const request = context.getRequest()
const userId = this.extractUserId(request)

if (!userId) {
throw new UnauthorizedException()
}

const user = await this.userService.findById(userId)
if (!user) {
throw new UnauthorizedException('User not found')
}

// Attach user to request
request.user = user
return true
}
}

Access in controller:

@Controller()
class ProfileController {
@Endpoint(getProfile)
async getProfile(params: EndpointParams<typeof getProfile>) {
const request = inject(Request)
return request.user // User attached by guard
}
}

Common Guard Patterns​

Simple Token Check​

@Injectable()
class ApiKeyGuard implements CanActivate {
private config = inject(ConfigService)

async canActivate(context: AbstractExecutionContext): Promise<boolean> {
const request = context.getRequest()
const apiKey = request.headers['x-api-key']
const validKey = this.config.getOrThrow('API_KEY')

if (apiKey !== validKey) {
throw new UnauthorizedException('Invalid API key')
}

return true
}
}

Role Check​

@Injectable()
class AdminGuard implements CanActivate {
async canActivate(context: AbstractExecutionContext): Promise<boolean> {
const request = context.getRequest()
const user = request.user // Set by AuthGuard

if (!user || user.role !== 'admin') {
throw new ForbiddenException('Admin access required')
}

return true
}
}

Next Steps​