Skip to content

Commit 963ccc1

Browse files
authored
Merge pull request #57 from topcoder-platform/use-jwks
Use JWKS to validate incoming JWT auth tokens
2 parents d7cf709 + e5114f7 commit 963ccc1

File tree

5 files changed

+144
-5
lines changed

5 files changed

+144
-5
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"csv-stringify": "^6.5.2",
3434
"dotenv": "^16.5.0",
3535
"jsonwebtoken": "^9.0.2",
36+
"jwks-rsa": "^3.2.0",
3637
"lodash": "^4.17.21",
3738
"reflect-metadata": "^0.2.2",
3839
"rxjs": "^7.8.1",

pnpm-lock.yaml

Lines changed: 87 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/config/config.env.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@ export class ConfigEnv {
3131
@IsString()
3232
AUTH0_M2M_GRANT_TYPE!: string;
3333

34-
@IsString()
35-
AUTH0_CERT!: string;
36-
3734
@IsString()
3835
AUTH0_CLIENT_ID!: string;
3936

src/core/auth/jwt.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Logger } from '@nestjs/common';
2+
import { decode } from 'jsonwebtoken';
3+
import { JwksClient } from 'jwks-rsa';
4+
import { ENV_CONFIG } from 'src/config';
5+
6+
const logger = new Logger(`auth/jwks`);
7+
8+
const client = new JwksClient({
9+
jwksUri: `${ENV_CONFIG.AUTH0_M2M_TOKEN_URL}/.well-known/jwks.json`,
10+
cache: true,
11+
rateLimit: true,
12+
});
13+
14+
/**
15+
* Retrieves the signing key for a given JWT token.
16+
*
17+
* This function decodes the token to extract its header and uses the `kid` (Key ID)
18+
* to fetch the corresponding signing key from a remote client. The signing key is
19+
* then resolved as a public key.
20+
*
21+
* @param token - The JWT token for which the signing key is to be retrieved.
22+
* @returns A promise that resolves with the public signing key as a string.
23+
* @throws An error if the token is invalid, the `kid` is missing, or the signing key
24+
* cannot be retrieved or resolved.
25+
*/
26+
export const getSigningKey = (token: string) => {
27+
const tokenHeader = decode(token, { complete: true })?.header;
28+
29+
return new Promise((resolve, reject) => {
30+
if (!tokenHeader || !tokenHeader.kid) {
31+
logger.error('Invalid token: Missing key ID');
32+
return reject(new Error('Invalid token: Missing key ID'));
33+
}
34+
35+
client.getSigningKey(tokenHeader.kid, function (err, key) {
36+
if (err || !key) {
37+
logger.error('Error getting signing key:', err);
38+
return reject(new Error('Invalid token: Unable to get signing key'));
39+
}
40+
41+
// Get the public key using the proper method
42+
const signingKey = key.getPublicKey();
43+
44+
if (!signingKey) {
45+
logger.error('Error getting public key!');
46+
return reject(new Error('Invalid token: Unable to get public key'));
47+
}
48+
49+
resolve(signingKey);
50+
});
51+
});
52+
};

src/core/auth/middleware/tokenValidator.middleware.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import {
66
} from '@nestjs/common';
77
import * as jwt from 'jsonwebtoken';
88
import { ENV_CONFIG } from 'src/config';
9+
import { getSigningKey } from '../jwt';
910

1011
const logger = new Logger(`Auth/TokenValidatorMiddleware`);
1112

1213
@Injectable()
1314
export class TokenValidatorMiddleware implements NestMiddleware {
14-
use(req: any, res: Response, next: (error?: any) => void) {
15+
async use(req: any, res: Response, next: (error?: any) => void) {
1516
const [type, idToken] = req.headers.authorization?.split(' ') ?? [];
1617

1718
if (type !== 'Bearer' || !idToken) {
@@ -20,7 +21,8 @@ export class TokenValidatorMiddleware implements NestMiddleware {
2021

2122
let decoded: any;
2223
try {
23-
decoded = jwt.verify(idToken, ENV_CONFIG.AUTH0_CERT);
24+
const signingKey = await getSigningKey(idToken);
25+
decoded = jwt.verify(idToken, signingKey);
2426
} catch (error) {
2527
logger.error('Error verifying JWT', error);
2628
throw new UnauthorizedException('Invalid or expired JWT!');

0 commit comments

Comments
 (0)