mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:41:09 +02:00
fix(mana-core-auth): use JWKS with jose for JWT verification
The JWT guards were using RS256 algorithm with jsonwebtoken library, but Better Auth generates EdDSA tokens. This caused all authenticated requests to fail with "Invalid token". Changes: - Replace jsonwebtoken with jose library - Use JWKS endpoint for key fetching instead of static publicKey - Support EdDSA algorithm used by Better Auth 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
8e414c12ba
commit
17313473aa
2 changed files with 42 additions and 27 deletions
|
|
@ -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<typeof createRemoteJWKSet> | null = null;
|
||||
|
||||
constructor(private configService: ConfigService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
|
|
@ -15,28 +23,31 @@ export class JwtAuthGuard implements CanActivate {
|
|||
}
|
||||
|
||||
try {
|
||||
const publicKey = this.configService.get<string>('jwt.publicKey');
|
||||
if (!publicKey) {
|
||||
throw new UnauthorizedException('JWT configuration error');
|
||||
// Lazy initialize JWKS
|
||||
if (!this.jwks) {
|
||||
const baseUrl = this.configService.get<string>('BASE_URL') || 'http://localhost:3001';
|
||||
const jwksUrl = new URL('/api/v1/auth/jwks', baseUrl);
|
||||
this.jwks = createRemoteJWKSet(jwksUrl);
|
||||
}
|
||||
const audience = this.configService.get<string>('jwt.audience');
|
||||
const issuer = this.configService.get<string>('jwt.issuer');
|
||||
|
||||
const payload = jwt.verify(token, publicKey, {
|
||||
algorithms: ['RS256'],
|
||||
audience,
|
||||
const issuer = this.configService.get<string>('jwt.issuer') || 'manacore';
|
||||
const audience = this.configService.get<string>('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');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<typeof createRemoteJWKSet> | null = null;
|
||||
|
||||
constructor(private configService: ConfigService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
|
|
@ -21,26 +25,26 @@ export class OptionalAuthGuard implements CanActivate {
|
|||
}
|
||||
|
||||
try {
|
||||
const publicKey = this.configService.get<string>('jwt.publicKey');
|
||||
if (!publicKey) {
|
||||
request.user = null;
|
||||
return true;
|
||||
// Lazy initialize JWKS
|
||||
if (!this.jwks) {
|
||||
const baseUrl = this.configService.get<string>('BASE_URL') || 'http://localhost:3001';
|
||||
const jwksUrl = new URL('/api/v1/auth/jwks', baseUrl);
|
||||
this.jwks = createRemoteJWKSet(jwksUrl);
|
||||
}
|
||||
|
||||
const audience = this.configService.get<string>('jwt.audience');
|
||||
const issuer = this.configService.get<string>('jwt.issuer');
|
||||
const issuer = this.configService.get<string>('jwt.issuer') || 'manacore';
|
||||
const audience = this.configService.get<string>('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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue