Auth Bypass in Nestjs with Bearer Tokens
Auth Bypass in Nestjs with Bearer Tokens — how this combination creates or exposes the vulnerability
In NestJS applications that use Bearer Tokens, an Auth Bypass often occurs when authorization checks are applied inconsistently or when routes intended to be public are mistakenly guarded—or not guarded—at the wrong layer. A common pattern is to enable Bearer Token extraction via an Auth Guard (e.g., JwtAuthGuard) and then rely solely on that guard without enforcing role-based or scope-based authorization in the route handler or controller. This can lead to Auth Bypass where a low-privilege token (or no token) gains access to admin-only endpoints because the guard validates the token’s authenticity but does not validate the associated permissions or scopes.
Another frequent cause is misconfigured CORS or misapplied global guards. If you apply guards at the controller level but omit them on specific routes, or if you use a custom Reflector to skip authorization based on metadata, an attacker can invoke those unprotected routes directly. For example, a route decorated with @UseGuards(JwtAuthGuard) on a class might be inadvertently overridden at the method level by another decorator that disables guards. Because middleBrick tests unauthenticated attack surfaces and checks Authorization and BOLA/IDOR, it can identify endpoints where token validation does not align with intended access controls.
Bearer Tokens also introduce risk when they are issued with broad scopes or when token introspection is not enforced server-side. In NestJS, if the JWT validation only checks the signature and expiry but does not validate scopes or resource ownership, an attacker can reuse a token meant for read-only actions to perform write operations. This becomes an Auth Bypass when combined with weak route-level authorization checks. For example, an endpoint like DELETE /users/:id might verify that a valid Bearer Token exists but fail to ensure that the token’s subject matches the :id or that the token has the delete:user scope. middleBrick’s checks for Property Authorization and BOLA/IDOR help surface these gaps by correlating spec definitions with runtime behavior.
Consider a NestJS controller where an endpoint lacks explicit role checks after JWT verification:
@UseGuards(JwtAuthGuard)@Get('users/:id')findOne(@Param('id') id: string, @Request() req) { return this.usersService.findOne(id);}If the JwtAuthGuard only validates the token and does not enforce role or scope checks, any authenticated user with a valid Bearer Token can access any user record. An attacker can trivially iterate over numeric IDs and read data, effectively bypassing intended authorization. This pattern is especially risky when combined with predictable IDs and missing ownership validation. middleBrick’s BOLA/IDOR and Property Authorization checks are designed to detect such inconsistencies between authentication and authorization.
Additionally, global guard configurations or improperly set guards on parent routes can unintentionally expose admin-only endpoints. For example, applying a guard at the module or application interceptor level without fine-grained control may allow non-privileged routes to inherit broader permissions. Always ensure that your guard chain and route decorators align with the principle of least privilege, and validate scopes, roles, and ownership within the handler or via a dedicated authorization layer.
Bearer Tokens-Specific Remediation in Nestjs — concrete code fixes
To remediate Auth Bypass risks with Bearer Tokens in NestJS, enforce strict token validation, scope checking, and ownership validation at the point of use. Do not rely on guards alone for authorization; combine them with explicit checks inside handlers or use a dedicated authorization layer such as Casl or RBAC decorators.
Below are concrete, working examples of secure Bearer Token usage in NestJS:
1. Guard with scope and role validation
Extend your JwtAuthGuard to inspect payload scopes and roles, and reject requests that do not meet the required permissions:
@Injectable()export class JwtAuthGuard implements CanActivate { constructor(private readonly reflector: Reflector) {} async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); const token = this.extractToken(request); if (!token) { throw new UnauthorizedException('Token missing'); } const payload = await this.validateToken(token); request.user = payload; const requiredScopes = this.reflector.get<string[]>('scopes', context.getHandler()); if (requiredScopes && !requiredScopes.some(s => payload.scopes.includes(s))) { throw new ForbiddenException('Insufficient scope'); } const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler()); if (requiredRoles && !requiredRoles.includes(payload.role)) { throw new ForbiddenException('Insufficient role'); } return true; } private extractToken(request: Request) { const [, token] = request.headers.authorization?.split(' ') ?? []; return token; } private async validateToken(token: string) { // verify signature and expiry, then return payload return { sub: 'user-123', role: 'user', scopes: ['read:users'] }; }}Use the guard with metadata-driven scope/role requirements:
@UseGuards(JwtAuthGuard)@Get('users/:id')@SetMetadata('roles', ['admin'])@SetMetadata('scopes', ['read:users:own'] )findOne(@Param('id') id: string, @Request() req) { if (req.user.sub !== id && !req.user.scopes.includes('read:users:all')) { throw new ForbiddenException('Not allowed to view this user'); } return this.usersService.findOne(id);}2. Validate ownership and scope in the handler
Even when a guard passes, always enforce ownership or scope checks inside the handler for sensitive operations:
@UseGuards(JwtAuthGuard)@Delete('users/:id')async deleteOne(@Param('id') id: string, @Request() req) { if (req.user.sub !== id && !req.user.scopes.includes('delete:users')) { throw new ForbiddenException('Cannot delete this user'); } return this.usersService.delete(id);}3. Use a centralized authorization service
For complex policies, inject an authorization service and check permissions explicitly:
@UseGuards(JwtAuthGuard)@Post('projects/:projectId/members')async addMember( @Param('projectId') projectId: string, @Body() dto: AddMemberDto, @Request() req) { const allowed = await this.authz.can(req.user).create('Member').onResource(`project:${projectId}`); if (!allowed) { throw new ForbiddenException('Unauthorized to add members'); } return this.projectsService.addMember(projectId, dto);}4. Secure Bearer Token transmission and storage
Ensure tokens are transmitted only over HTTPS and avoid storing them in insecure locations. On the client, use HttpOnly cookies or secure storage mechanisms. In NestJS, enforce HTTPS in production and set secure cookie flags if using cookie-based sessions.
By combining properly configured guards with per-handler authorization and secure token handling, you can eliminate Auth Bypass risks associated with Bearer Tokens in NestJS. middleBrick’s checks for Authentication, Authorization, and BOLA/IDOR validate these patterns and help ensure that token validation and permissions are consistently enforced.
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |