diff --git a/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts b/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts index 366d89743..433f573a4 100644 --- a/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts +++ b/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts @@ -1,9 +1,17 @@ import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import * as jwt from 'jsonwebtoken'; +import { jwtVerify, createRemoteJWKSet, type JWTPayload } from 'jose'; +/** + * JWT Auth Guard using JWKS (Better Auth compatible) + * + * Uses jose library with JWKS endpoint for EdDSA token verification. + * This is the correct approach for Better Auth which uses EdDSA keys. + */ @Injectable() export class JwtAuthGuard implements CanActivate { + private jwks: ReturnType | null = null; + constructor(private configService: ConfigService) {} async canActivate(context: ExecutionContext): Promise { @@ -15,28 +23,31 @@ export class JwtAuthGuard implements CanActivate { } try { - const publicKey = this.configService.get('jwt.publicKey'); - if (!publicKey) { - throw new UnauthorizedException('JWT configuration error'); + // Lazy initialize JWKS + if (!this.jwks) { + const baseUrl = this.configService.get('BASE_URL') || 'http://localhost:3001'; + const jwksUrl = new URL('/api/v1/auth/jwks', baseUrl); + this.jwks = createRemoteJWKSet(jwksUrl); } - const audience = this.configService.get('jwt.audience'); - const issuer = this.configService.get('jwt.issuer'); - const payload = jwt.verify(token, publicKey, { - algorithms: ['RS256'], - audience, + const issuer = this.configService.get('jwt.issuer') || 'manacore'; + const audience = this.configService.get('jwt.audience') || 'manacore'; + + const { payload } = await jwtVerify(token, this.jwks, { issuer, - }) as jwt.JwtPayload; + audience, + }); // Attach user to request request.user = { userId: payload.sub, - email: payload.email, - role: payload.role, + email: payload.email as string, + role: payload.role as string, }; return true; } catch (error) { + console.debug('[JwtAuthGuard] Token verification failed:', error); throw new UnauthorizedException('Invalid token'); } } diff --git a/services/mana-core-auth/src/common/guards/optional-auth.guard.ts b/services/mana-core-auth/src/common/guards/optional-auth.guard.ts index 6a37722cf..5af0ec609 100644 --- a/services/mana-core-auth/src/common/guards/optional-auth.guard.ts +++ b/services/mana-core-auth/src/common/guards/optional-auth.guard.ts @@ -1,13 +1,17 @@ import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import * as jwt from 'jsonwebtoken'; +import { jwtVerify, createRemoteJWKSet } from 'jose'; /** - * Optional authentication guard - * Attaches user to request if valid token is present, but doesn't require it + * Optional authentication guard using JWKS (Better Auth compatible) + * + * Attaches user to request if valid token is present, but doesn't require it. + * Uses jose library with JWKS endpoint for EdDSA token verification. */ @Injectable() export class OptionalAuthGuard implements CanActivate { + private jwks: ReturnType | null = null; + constructor(private configService: ConfigService) {} async canActivate(context: ExecutionContext): Promise { @@ -21,26 +25,26 @@ export class OptionalAuthGuard implements CanActivate { } try { - const publicKey = this.configService.get('jwt.publicKey'); - if (!publicKey) { - request.user = null; - return true; + // Lazy initialize JWKS + if (!this.jwks) { + const baseUrl = this.configService.get('BASE_URL') || 'http://localhost:3001'; + const jwksUrl = new URL('/api/v1/auth/jwks', baseUrl); + this.jwks = createRemoteJWKSet(jwksUrl); } - const audience = this.configService.get('jwt.audience'); - const issuer = this.configService.get('jwt.issuer'); + const issuer = this.configService.get('jwt.issuer') || 'manacore'; + const audience = this.configService.get('jwt.audience') || 'manacore'; - const payload = jwt.verify(token, publicKey, { - algorithms: ['RS256'], - audience, + const { payload } = await jwtVerify(token, this.jwks, { issuer, - }) as jwt.JwtPayload; + audience, + }); // Attach user to request request.user = { userId: payload.sub, - email: payload.email, - role: payload.role, + email: payload.email as string, + role: payload.role as string, }; } catch { // Invalid token - allow request but no user