Skip to content

Commit 0d7c792

Browse files
committed
fix: Fix Keycloak JWT authentication in production
🔐 Fix Production Authentication ## Issue Fixed: - 401 Unauthorized errors in production - JWT strategy trying to validate Keycloak tokens with wrong secret - Keycloak tokens signed by Keycloak, not our JWT_SECRET ## Solution: - Modified JwtAuthGuard to validate Keycloak tokens directly - Bypass JWT strategy validation for production - Use AuthService.verifyToken() to validate with Keycloak - Extract Bearer token from Authorization header ## Key Changes: - JwtAuthGuard: Direct Keycloak token validation - JwtStrategy: Simplified for development mode only - Production: Validates tokens against Keycloak userinfo endpoint - Development: Still uses DISABLE_AUTH bypass ## Authentication Flow: 1. Extract Bearer token from Authorization header 2. Call Keycloak userinfo endpoint with token 3. Parse user information from Keycloak response 4. Set request.user with validated user data This fixes the 401 errors by properly validating Keycloak JWT tokens in production environment.
1 parent adac7bd commit 0d7c792

2 files changed

Lines changed: 36 additions & 10 deletions

File tree

nodebook-base/src/auth/guards/jwt-auth.guard.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
import { Injectable, ExecutionContext } from '@nestjs/common';
1+
import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common';
22
import { AuthGuard } from '@nestjs/passport';
33
import { ConfigService } from '@nestjs/config';
4+
import { AuthService } from '../auth.service.js';
45

56
@Injectable()
67
export class JwtAuthGuard extends AuthGuard('jwt') {
7-
constructor(private configService: ConfigService) {
8+
constructor(
9+
private configService: ConfigService,
10+
private authService: AuthService,
11+
) {
812
super();
913
}
1014

11-
canActivate(context: ExecutionContext) {
15+
async canActivate(context: ExecutionContext) {
1216
// Skip authentication in development mode
1317
if (this.configService.get<string>('DISABLE_AUTH') === 'true') {
1418
const request = context.switchToHttp().getRequest();
@@ -21,7 +25,27 @@ export class JwtAuthGuard extends AuthGuard('jwt') {
2125
return true;
2226
}
2327

24-
return super.canActivate(context);
28+
// For production, validate Keycloak token directly
29+
const request = context.switchToHttp().getRequest();
30+
const authHeader = request.headers.authorization;
31+
32+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
33+
throw new UnauthorizedException('No token provided');
34+
}
35+
36+
const token = authHeader.substring(7); // Remove 'Bearer ' prefix
37+
38+
try {
39+
const user = await this.authService.verifyToken(token);
40+
if (!user) {
41+
throw new UnauthorizedException('Invalid token');
42+
}
43+
44+
request.user = user;
45+
return true;
46+
} catch (error) {
47+
throw new UnauthorizedException('Token validation failed');
48+
}
2549
}
2650
}
2751

nodebook-base/src/auth/strategies/jwt.strategy.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
2828
};
2929
}
3030

31-
// For production, validate with Keycloak
32-
const user = await this.authService.verifyToken(payload.access_token || payload.token);
33-
if (!user) {
34-
throw new UnauthorizedException();
35-
}
36-
return user;
31+
// For production, this should not be called since we handle validation in the guard
32+
// But if it is called, return the payload as user
33+
return {
34+
id: payload.sub || payload.id,
35+
username: payload.preferred_username || payload.username,
36+
email: payload.email,
37+
isAdmin: payload.realm_access?.roles?.includes('admin') || false,
38+
};
3739
}
3840
}
3941

0 commit comments

Comments
 (0)