|
1 |
| -const axios = require("axios"); |
2 |
| -const jwt = require("jsonwebtoken"); |
3 |
| -const jwkToPem = require("jwk-to-pem"); |
4 |
| - |
5 |
| -/** |
6 |
| - * Obtain the JSON Web Key Set (JWKS) from the OIDC authority. |
7 |
| - * @param {string} authorityUrl the OIDC authority URL. e.g. https://login.microsoftonline.com/{tenantId} |
8 |
| - * @return {Promise<object[]>} the JWKS keys |
9 |
| - */ |
10 |
| -async function getJwks(authorityUrl) { |
11 |
| - try { |
12 |
| - const { data } = await axios.get(`${authorityUrl}/.well-known/openid-configuration`); |
13 |
| - const jwksUri = data.jwks_uri; |
14 |
| - |
15 |
| - const { data: jwks } = await axios.get(jwksUri); |
16 |
| - return jwks.keys; |
17 |
| - } catch (error) { |
18 |
| - console.error("Error fetching JWKS:", error); |
19 |
| - throw new Error("Failed to fetch JWKS"); |
20 |
| - } |
21 |
| -} |
22 |
| - |
23 |
| -/** |
24 |
| - * Validate a JWT token using the OIDC configuration. |
25 |
| - * @param {*} token the JWT token |
26 |
| - * @param {*} authorityUrl the OIDC authority URL |
27 |
| - * @param {*} clientID the OIDC client ID |
28 |
| - * @param {*} expectedAudience the expected audience for the token |
29 |
| - * @return {Promise<object>} the verified payload or an error |
30 |
| - */ |
31 |
| -async function validateJwt(token, authorityUrl, clientID, expectedAudience) { |
32 |
| - try { |
33 |
| - const jwks = await getJwks(authorityUrl); |
34 |
| - |
35 |
| - const decodedHeader = await jwt.decode(token, { complete: true }); |
36 |
| - if (!decodedHeader || !decodedHeader.header || !decodedHeader.header.kid) { |
37 |
| - throw new Error("Invalid JWT: Missing key ID (kid)"); |
38 |
| - } |
39 |
| - |
40 |
| - const { kid } = decodedHeader.header; |
41 |
| - const jwk = jwks.find((key) => key.kid === kid); |
42 |
| - if (!jwk) { |
43 |
| - throw new Error("No matching key found in JWKS"); |
44 |
| - } |
45 |
| - |
46 |
| - const pubKey = jwkToPem(jwk); |
47 |
| - |
48 |
| - const verifiedPayload = jwt.verify(token, pubKey, { |
49 |
| - algorithms: ["RS256"], |
50 |
| - issuer: authorityUrl, |
51 |
| - audience: expectedAudience, |
52 |
| - }); |
53 |
| - |
54 |
| - if (verifiedPayload.azp !== clientID) { |
55 |
| - throw new Error("JWT client ID does not match"); |
56 |
| - } |
57 |
| - |
58 |
| - return { verifiedPayload }; |
59 |
| - } catch (error) { |
60 |
| - const errorMessage = `JWT validation failed: ${error.message}\n`; |
61 |
| - console.error(errorMessage); |
62 |
| - return { error: errorMessage }; |
63 |
| - } |
64 |
| -} |
65 |
| - |
66 |
| -/** |
67 |
| - * Assign roles to the user based on the role mappings provided in the jwtConfig. |
68 |
| - * |
69 |
| - * If no role mapping is provided, the user will not have any roles assigned (i.e. user.admin = false). |
70 |
| - * @param {*} roleMapping the role mapping configuration |
71 |
| - * @param {*} payload the JWT payload |
72 |
| - * @param {*} user the req.user object to assign roles to |
73 |
| - */ |
74 |
| -function assignRoles(roleMapping, payload, user) { |
75 |
| - if (roleMapping) { |
76 |
| - for (const role of Object.keys(roleMapping)) { |
77 |
| - const claimValuePair = roleMapping[role]; |
78 |
| - const claim = Object.keys(claimValuePair)[0]; |
79 |
| - const value = claimValuePair[claim]; |
80 |
| - |
81 |
| - if (payload[claim] && payload[claim] === value) { |
82 |
| - user[role] = true; |
83 |
| - } |
84 |
| - } |
85 |
| - } |
86 |
| -} |
87 |
| - |
88 | 1 | const jwtAuthHandler = () => {
|
89 | 2 | return async (req, res, next) => {
|
90 | 3 | const apiAuthMethods = require('../../config').getAPIAuthMethods();
|
|
0 commit comments