1- const axios = require ( "axios" ) ;
2- const jwt = require ( "jsonwebtoken" ) ;
3- const jwkToPem = require ( "jwk-to-pem" ) ;
4- const config = require ( '../../config' ) ;
1+ const { assignRoles, validateJwt } = require ( './jwtUtils' ) ;
52
63/**
7- * Obtain the JSON Web Key Set (JWKS) from the OIDC authority .
8- * @param {string } authorityUrl the OIDC authority URL. e.g. https://login.microsoftonline.com/{tenantId}
9- * @return {Promise<object[]> } the JWKS keys
4+ * Middleware function to handle JWT authentication .
5+ * @param {* } overrideConfig optional configuration to override the default JWT configuration ( e.g. for testing)
6+ * @return {Function } the middleware function
107 */
11- async function getJwks ( authorityUrl ) {
12- try {
13- const { data } = await axios . get ( `${ authorityUrl } /.well-known/openid-configuration` ) ;
14- const jwksUri = data . jwks_uri ;
15-
16- const { data : jwks } = await axios . get ( jwksUri ) ;
17- return jwks . keys ;
18- } catch ( error ) {
19- console . error ( "Error fetching JWKS:" , error ) ;
20- throw new Error ( "Failed to fetch JWKS" ) ;
21- }
22- }
23-
24- /**
25- * Validate a JWT token using the OIDC configuration.
26- * @param {* } token the JWT token
27- * @param {* } authorityUrl the OIDC authority URL
28- * @param {* } clientID the OIDC client ID
29- * @param {* } expectedAudience the expected audience for the token
30- * @return {Promise<object> } the verified payload or an error
31- */
32- async function validateJwt ( token , authorityUrl , clientID , expectedAudience ) {
33- try {
34- const jwks = await getJwks ( authorityUrl ) ;
35-
36- const decodedHeader = await jwt . decode ( token , { complete : true } ) ;
37- if ( ! decodedHeader || ! decodedHeader . header || ! decodedHeader . header . kid ) {
38- throw new Error ( "Invalid JWT: Missing key ID (kid)" ) ;
39- }
40-
41- const { kid } = decodedHeader . header ;
42- const jwk = jwks . find ( ( key ) => key . kid === kid ) ;
43- if ( ! jwk ) {
44- throw new Error ( "No matching key found in JWKS" ) ;
45- }
46-
47- const pubKey = jwkToPem ( jwk ) ;
48-
49- const verifiedPayload = jwt . verify ( token , pubKey , {
50- algorithms : [ "RS256" ] ,
51- issuer : authorityUrl ,
52- audience : expectedAudience ,
53- } ) ;
54-
55- if ( verifiedPayload . azp !== clientID ) {
56- throw new Error ( "JWT client ID does not match" ) ;
57- }
58-
59- return { verifiedPayload } ;
60- } catch ( error ) {
61- const errorMessage = `JWT validation failed: ${ error . message } \n` ;
62- console . error ( errorMessage ) ;
63- return { error : errorMessage } ;
64- }
65- }
66-
67- const jwtAuthHandler = ( ) => {
8+ const jwtAuthHandler = ( overrideConfig = null ) => {
689 return async ( req , res , next ) => {
69- const apiAuthMethods = config . getAPIAuthMethods ( ) ;
10+ const apiAuthMethods =
11+ overrideConfig
12+ ? [ { type : "jwt" , jwtConfig : overrideConfig } ]
13+ : require ( '../../config' ) . getAPIAuthMethods ( ) ;
14+
7015 const jwtAuthMethod = apiAuthMethods . find ( ( method ) => method . type . toLowerCase ( ) === "jwt" ) ;
71- if ( ! jwtAuthMethod ) {
16+ if ( ! overrideConfig && ( ! jwtAuthMethod || ! jwtAuthMethod . enabled ) ) {
7217 return next ( ) ;
7318 }
7419
@@ -81,7 +26,7 @@ const jwtAuthHandler = () => {
8126 return res . status ( 401 ) . send ( "No token provided\n" ) ;
8227 }
8328
84- const { clientID, authorityURL, expectedAudience } = jwtAuthMethod . jwtConfig ;
29+ const { clientID, authorityURL, expectedAudience, roleMapping } = jwtAuthMethod . jwtConfig ;
8530 const audience = expectedAudience || clientID ;
8631
8732 if ( ! authorityURL ) {
@@ -99,6 +44,8 @@ const jwtAuthHandler = () => {
9944 }
10045
10146 req . user = verifiedPayload ;
47+ assignRoles ( roleMapping , verifiedPayload , req . user ) ;
48+
10249 return next ( ) ;
10350 }
10451}
0 commit comments