Guards API Reference
Overview
Guards control access to tools, resources, and prompts by determining whether a request should proceed. They implement authorization logic, verify authentication tokens, check permissions, and enforce access policies.
Guards execute before the handler and can deny access by returning false or throwing an exception.
Table of Contents
Guard Interface
Typescript
interface Guard {
canActivate(context: ExecutionContext): Promise<boolean> | boolean;
}
Parameters:
context: The execution context containing auth data, logger, and metadata
Returns:
true: Allow the request to proceedfalse: Deny access (throws ForbiddenException)throw Error: Deny access with custom error
Creating Guards
Basic Guard
Typescript
import { Guard, ExecutionContext, Injectable } from '@nitrostack/core';
@Injectable({ deps: [] })
export class AuthGuard implements Guard {
async canActivate(context: ExecutionContext): Promise<boolean> {
// Check if user is authenticated
if (!context.auth?.subject) {
return false; // Deny access
}
return true; // Allow access
}
}
Role-Based Guard
Typescript
@Injectable({ deps: [] })
export class AdminGuard implements Guard {
async canActivate(context: ExecutionContext): Promise<boolean> {
const roles = context.auth?.roles as string[] | undefined;
if (!roles || !roles.includes('admin')) {
context.logger.warn('Admin access denied', {
userId: context.auth?.subject,
roles
});
return false;
}
return true;
}
}
Permission-Based Guard
Typescript
@Injectable({ deps: [] })
export class PermissionGuard implements Guard {
constructor(private requiredPermission: string) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const permissions = context.auth?.permissions as string[] | undefined;
return permissions?.includes(this.requiredPermission) ?? false;
}
}
// Factory function for different permissions
export function RequirePermission(permission: string) {
return class extends PermissionGuard {
constructor() {
super(permission);
}
};
}
// Usage
@UseGuards(RequirePermission('products:write'))
async createProduct(input: any, ctx: ExecutionContext) {
// ...
}
Clearance Level Guard
Typescript
type ClearanceLevel = 'public' | 'crew' | 'officer' | 'commander' | 'captain';
const CLEARANCE_HIERARCHY: Record<ClearanceLevel, number> = {
public: 0,
crew: 1,
officer: 2,
commander: 3,
captain: 4
};
@Injectable({ deps: [] })
export class ClearanceGuard implements Guard {
constructor(private requiredLevel: ClearanceLevel) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const userLevel = (context.auth?.clearance as ClearanceLevel) || 'public';
const userRank = CLEARANCE_HIERARCHY[userLevel] ?? 0;
const requiredRank = CLEARANCE_HIERARCHY[this.requiredLevel];
if (userRank < requiredRank) {
context.logger.warn('Clearance check failed', {
userId: context.auth?.subject,
userLevel,
requiredLevel: this.requiredLevel
});
return false;
}
context.logger.info('Clearance check passed', {
userLevel,
requiredLevel: this.requiredLevel
});
return true;
}
}
// Factory for specific clearance levels
export class OfficerClearanceGuard extends ClearanceGuard {
constructor() { super('officer'); }
}
export class CommanderClearanceGuard extends ClearanceGuard {
constructor() { super('commander'); }
}
Using Guards
On Individual Methods
Typescript
import { ToolDecorator as Tool, UseGuards } from '@nitrostack/core';
import { AuthGuard } from './guards/auth.guard.js';
export class UserTools {
@Tool({ name: 'get_profile' })
@UseGuards(AuthGuard)
async getProfile(input: {}, ctx: ExecutionContext) {
return this.userService.findById(ctx.auth!.subject);
}
}
Multiple Guards
Guards execute in order. All must pass for the request to proceed:
Typescript
@Tool({ name: 'delete_user' })
@UseGuards(AuthGuard, AdminGuard, RateLimitGuard)
async deleteUser(input: { userId: string }, ctx: ExecutionContext) {
// Only authenticated admins within rate limit can access
return this.userService.delete(input.userId);
}
On Resources and Prompts
Guards work on all MCP primitives:
Typescript
@Resource({
uri: 'admin://dashboard',
name: 'Admin Dashboard'
})
@UseGuards(AdminGuard)
async getAdminDashboard(uri: string, ctx: ExecutionContext) {
// Admin-only resource
}
@Prompt({
name: 'confidential_analysis',
title: 'Confidential Analysis',
description: 'Analyze confidential data'
})
@UseGuards(OfficerClearanceGuard)
async getConfidentialPrompt(args: any, ctx: ExecutionContext) {
// Officer-level prompt
}
Common Patterns
JWT Token Validation
Typescript
import jwt from 'jsonwebtoken';
@Injectable({ deps: [] })
export class JWTGuard implements Guard {
private readonly secret = process.env.JWT_SECRET!;
async canActivate(context: ExecutionContext): Promise<boolean> {
const token = context.auth?.token;
if (!token) {
throw new Error('No authentication token provided');
}
try {
const decoded = jwt.verify(token, this.secret) as jwt.JwtPayload;
// Enrich context with decoded data
if (context.auth) {
context.auth.subject = decoded.sub;
context.auth.roles = decoded.roles;
context.auth.permissions = decoded.permissions;
}
return true;
} catch (error) {
throw new Error('Invalid or expired token');
}
}
}
API Key Validation
Typescript
@Injectable({ deps: [] })
export class ApiKeyGuard implements Guard {
private readonly validKeys = new Set(
(process.env.API_KEYS || '').split(',').filter(Boolean)
);
async canActivate(context: ExecutionContext): Promise<boolean> {
const apiKey = context.auth?.token || context.metadata?.apiKey;
if (!apiKey || !this.validKeys.has(apiKey as string)) {
context.logger.warn('Invalid API key', {
keyProvided: !!apiKey
});
return false;
}
return true;
}
}
Rate Limit Guard
Typescript
@Injectable({ deps: [] })
export class RateLimitGuard implements Guard {
private requests = new Map<string, { count: number; resetAt: number }>();
private readonly limit = 100;
private readonly windowMs = 60000; // 1 minute
async canActivate(context: ExecutionContext): Promise<boolean> {
const key = context.auth?.subject || context.requestId || 'anonymous';
const now = Date.now();
let entry = this.requests.get(key);
if (!entry || now > entry.resetAt) {
entry = { count: 0, resetAt: now + this.windowMs };
}
entry.count++;
this.requests.set(key, entry);
if (entry.count > this.limit) {
context.logger.warn('Rate limit exceeded', { key, count: entry.count });
throw new Error('Rate limit exceeded. Please try again later.');
}
return true;
}
}
Time-Based Access
Typescript
@Injectable({ deps: [] })
export class BusinessHoursGuard implements Guard {
async canActivate(context: ExecutionContext): Promise<boolean> {
const now = new Date();
const hour = now.getUTCHours();
const day = now.getUTCDay();
// Allow access Monday-Friday, 9am-5pm UTC
const isWeekday = day >= 1 && day <= 5;
const isBusinessHours = hour >= 9 && hour < 17;
if (!isWeekday || !isBusinessHours) {
context.logger.info('Access denied outside business hours');
return false;
}
return true;
}
}
Conditional Guard
Typescript
@Injectable({ deps: [] })
export class MaintenanceModeGuard implements Guard {
private readonly bypassRoles = ['admin', 'operator'];
async canActivate(context: ExecutionContext): Promise<boolean> {
const isMaintenanceMode = process.env.MAINTENANCE_MODE === 'true';
if (!isMaintenanceMode) {
return true; // Not in maintenance mode
}
// Allow admins to bypass
const roles = context.auth?.roles as string[] | undefined;
const canBypass = roles?.some(role => this.bypassRoles.includes(role));
if (!canBypass) {
throw new Error('System is under maintenance. Please try again later.');
}
return true;
}
}
Dependency Injection
Guards can inject services:
Typescript
@Injectable({ deps: [UserService, ConfigService] })
export class SubscriptionGuard implements Guard {
constructor(
private userService: UserService,
private configService: ConfigService
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const userId = context.auth?.subject;
if (!userId) return false;
const user = await this.userService.findById(userId);
if (!user) return false;
const requiredPlan = this.configService.get('REQUIRED_PLAN', 'pro');
if (user.subscriptionPlan !== requiredPlan && user.subscriptionPlan !== 'enterprise') {
context.logger.info('Subscription check failed', {
userId,
userPlan: user.subscriptionPlan,
requiredPlan
});
return false;
}
return true;
}
}
Best Practices
1. Fail Securely
Default to denying access:
Typescript
// Correct: Deny by default
async canActivate(context: ExecutionContext): Promise<boolean> {
try {
return await this.validateToken(context.auth?.token);
} catch {
return false; // Deny on any error
}
}
// Incorrect: Allow by default
async canActivate(context: ExecutionContext): Promise<boolean> {
try {
return await this.validateToken(context.auth?.token);
} catch {
return true; // Security hole!
}
}
2. Provide Meaningful Errors
Typescript
// Good: Descriptive error
async canActivate(context: ExecutionContext): Promise<boolean> {
if (!context.auth?.token) {
throw new Error('Authentication required. Please provide a valid token.');
}
if (!this.hasPermission(context)) {
throw new Error('Insufficient permissions for this operation.');
}
return true;
}
// Avoid: Generic error
async canActivate(context: ExecutionContext): Promise<boolean> {
return !!context.auth?.token && this.hasPermission(context);
}
3. Log Security Events
Typescript
async canActivate(context: ExecutionContext): Promise<boolean> {
const userId = context.auth?.subject;
if (!this.isAuthorized(context)) {
context.logger.warn('Authorization failed', {
userId,
toolName: context.toolName,
requestId: context.requestId,
reason: 'insufficient_permissions'
});
return false;
}
context.logger.info('Authorization granted', { userId });
return true;
}
4. Keep Guards Focused
Typescript
// Correct: Single responsibility
@UseGuards(
AuthenticationGuard, // Verify identity
RoleGuard, // Check roles
RateLimitGuard // Enforce limits
)
// Incorrect: Monolithic guard
@UseGuards(DoEverythingGuard)
5. Order Guards Appropriately
Typescript
// Recommended order
@UseGuards(
MaintenanceModeGuard, // 1. Check system availability
AuthenticationGuard, // 2. Verify identity
AuthorizationGuard, // 3. Check permissions
RateLimitGuard // 4. Enforce rate limits
)
6. Use Explicit Dependencies
For ESM compatibility, always declare dependencies:
Typescript
// Correct: Explicit deps
@Injectable({ deps: [UserService] })
export class OwnershipGuard implements Guard {
constructor(private userService: UserService) {}
}
// Avoid: Implicit deps (may fail in ESM)
@Injectable()
export class OwnershipGuard implements Guard {
constructor(private userService: UserService) {}
}
Related Documentation
- Tools Guide - Using guards with tools
- Resources Guide - Protected resources
- Middleware Guide - Middleware pipeline
- Authentication Overview - Auth patterns
- API Key Authentication - API key guards
- OAuth Authentication - OAuth guards