Skip to content

Commit cb35303

Browse files
committed
Added original configuration fields back for compatibility with providers not having a well-known endpoint
1 parent b0877ed commit cb35303

File tree

3 files changed

+122
-107
lines changed

3 files changed

+122
-107
lines changed

server/helpers/jwt.js

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
1-
const jwt = require('jsonwebtoken');
2-
const jwksClient = require('jwks-rsa');
1+
const jwt = require('jsonwebtoken')
2+
const jwksClient = require('jwks-rsa')
33

44
/**
55
* Function to get the signing key for a specific token.
66
* @param {Object} header - JWT header containing the `kid`.
77
* @returns {Promise<string>} - Resolves with the signing key.
88
*/
99
function getSigningKey(header, jwksUri) {
10-
return new Promise((resolve, reject) => {
11-
const client = jwksClient({ jwksUri });
12-
client.getSigningKey(header.kid, (err, key) => {
13-
if (err) {
14-
return reject('Error getting signing key:' + err);
15-
}
16-
const signingKey = key.getPublicKey();
17-
resolve(signingKey);
18-
});
19-
});
10+
return new Promise((resolve, reject) => {
11+
const client = jwksClient({ jwksUri })
12+
client.getSigningKey(header.kid, (err, key) => {
13+
if (err) {
14+
return reject(new Error('Error getting signing key: ' + err))
15+
}
16+
resolve(key.getPublicKey())
17+
})
18+
})
2019
}
2120

2221
/**
@@ -26,23 +25,23 @@ function getSigningKey(header, jwksUri) {
2625
* @returns {Promise<Object>} - Resolves with the decoded token if verification is successful.
2726
*/
2827
async function verifyJwt(token, conf) {
29-
try {
30-
const decodedHeader = jwt.decode(token, { complete: true });
31-
if (!decodedHeader || !decodedHeader.header) {
32-
throw new Error('JWT verification failed: Invalid token header');
33-
}
34-
const signingKey = await getSigningKey(decodedHeader.header, conf.jwksUri);
35-
const decoded = jwt.verify(token, signingKey, {
36-
algorithms: conf.algorithms || ['RS256'],
37-
issuer: conf.issuer,
38-
audience: conf.clientId
39-
});
40-
return decoded;
41-
} catch (err) {
42-
throw new Error('JWT verification failed: ' + err.message);
28+
try {
29+
const decodedHeader = jwt.decode(token, { complete: true })
30+
if (!decodedHeader || !decodedHeader.header) {
31+
throw new Error('JWT verification failed: Invalid token header')
4332
}
33+
const signingKey = await getSigningKey(decodedHeader.header, conf.jwksUri)
34+
const decoded = jwt.verify(token, signingKey, {
35+
algorithms: conf.algorithms || ['RS256'],
36+
issuer: conf.issuer,
37+
audience: conf.clientId
38+
})
39+
return decoded
40+
} catch (err) {
41+
throw new Error('JWT verification failed: ' + err.message)
42+
}
4443
}
4544

4645
module.exports = {
47-
verifyJwt
48-
};
46+
verifyJwt
47+
}
Lines changed: 68 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const _ = require('lodash')
22
const { verifyJwt } = require('../../../helpers/jwt')
3-
43
/* global WIKI */
54

65
// ------------------------------------
@@ -10,71 +9,81 @@ const { verifyJwt } = require('../../../helpers/jwt')
109
const OpenIDConnectStrategy = require('passport-openidconnect').Strategy
1110

1211
module.exports = {
13-
async init (passport, conf) {
12+
async init(passport, conf) {
1413
try {
15-
const response = await fetch(conf.wellKnownURL)
16-
if (!response.ok) throw new Error(`Failed to fetch well-known config: ${response.statusText}`)
17-
const wellKnown = await response.json()
18-
19-
passport.use(conf.key,
20-
new OpenIDConnectStrategy({
21-
issuer: wellKnown.issuer,
22-
authorizationURL: wellKnown.authorization_endpoint,
23-
tokenURL: wellKnown.token_endpoint,
24-
userInfoURL: wellKnown.userinfo_endpoint,
25-
clientID: conf.clientId,
26-
clientSecret: conf.clientSecret,
27-
callbackURL: conf.callbackURL,
28-
scope: conf.scope,
29-
passReqToCallback: true,
30-
skipUserProfile: conf.skipUserProfile,
31-
acrValues: conf.acrValues
32-
}, async (req, iss, uiProfile, idProfile, context, idToken, accessToken, refreshToken, params, cb) => {
33-
let idTokenClaims = {}
34-
if (conf.mergeIdTokenClaims && idToken) {
35-
idTokenClaims = await verifyJwt(idToken, {
36-
issuer: wellKnown.issuer,
37-
clientId: conf.clientId,
38-
jwksUri: wellKnown.jwks_uri,
39-
algorithms: wellKnown.id_token_signing_alg_values_supported
40-
})
41-
}
42-
// Merge claims from ID token and profile, with idProfile taking precedence
43-
const profile = { ...idTokenClaims, ...idProfile }
44-
try {
45-
const user = await WIKI.models.users.processProfile({
46-
providerKey: req.params.strategy,
47-
profile: {
48-
...profile,
49-
id: _.get(profile, conf.userIdClaim),
50-
displayName: _.get(profile, conf.displayNameClaim, '???'),
51-
email: _.get(profile, conf.emailClaim),
14+
let oidcConfig = {
15+
issuer: conf.issuer,
16+
authorizationURL: conf.authorizationURL,
17+
tokenURL: conf.tokenURL,
18+
userInfoURL: conf.userInfoURL,
19+
clientID: conf.clientId,
20+
clientSecret: conf.clientSecret,
21+
callbackURL: conf.callbackURL,
22+
scope: conf.scope,
23+
passReqToCallback: true,
24+
skipUserProfile: conf.skipUserProfile,
25+
acrValues: conf.acrValues
26+
}
27+
if (conf.wellKnownURL) {
28+
try {
29+
const response = await fetch(conf.wellKnownURL)
30+
if (!response.ok) throw new Error(response.statusText)
31+
const wellKnown = await response.json()
32+
if (!oidcConfig.issuer) oidcConfig.issuer = wellKnown.issuer
33+
if (!oidcConfig.authorizationURL) oidcConfig.authorizationURL = wellKnown.authorization_endpoint
34+
if (!oidcConfig.tokenURL) oidcConfig.tokenURL = wellKnown.token_endpoint
35+
if (!oidcConfig.userInfoURL) oidcConfig.userInfoURL = wellKnown.userinfo_endpoint
36+
oidcConfig.jwksUri = wellKnown.jwks_uri
37+
oidcConfig.idTokenSigningAlgValuesSupported = wellKnown.id_token_signing_alg_values_supported
38+
} catch (error) {
39+
WIKI.logger.error('Error fetching OIDC well-known configuration:', error)
40+
}
41+
}
42+
passport.use(conf.key, new OpenIDConnectStrategy(oidcConfig, async (req, iss, uiProfile, idProfile, context, idToken, accessToken, refreshToken, params, cb) => {
43+
let idTokenClaims = {}
44+
if (conf.mergeIdTokenClaims && idToken) {
45+
idTokenClaims = await verifyJwt(idToken, {
46+
issuer: oidcConfig.issuer,
47+
clientId: oidcConfig.clientID,
48+
jwksUri: oidcConfig.jwksUri,
49+
algorithms: oidcConfig.idTokenSigningAlgValuesSupported
50+
})
51+
}
52+
// Merge claims from ID token and profile, with idProfile taking precedence
53+
const profile = { ...idTokenClaims, ...idProfile }
54+
try {
55+
const user = await WIKI.models.users.processProfile({
56+
providerKey: req.params.strategy,
57+
profile: {
58+
...profile,
59+
id: _.get(profile, conf.userIdClaim),
60+
displayName: _.get(profile, conf.displayNameClaim, 'Unknown User'),
61+
email: _.get(profile, conf.emailClaim)
62+
}
63+
})
64+
if (conf.mapGroups) {
65+
const groups = _.get(profile, conf.groupsClaim)
66+
if (groups && _.isArray(groups)) {
67+
const currentGroups = (await user.$relatedQuery('groups').select('groups.id')).map(g => g.id)
68+
const expectedGroups = Object.values(WIKI.auth.groups).filter(g => groups.includes(g.name)).map(g => g.id)
69+
for (const groupId of _.difference(expectedGroups, currentGroups)) {
70+
await user.$relatedQuery('groups').relate(groupId)
5271
}
53-
})
54-
if (conf.mapGroups) {
55-
const groups = _.get(profile, conf.groupsClaim)
56-
if (groups && _.isArray(groups)) {
57-
const currentGroups = (await user.$relatedQuery('groups').select('groups.id')).map(g => g.id)
58-
const expectedGroups = Object.values(WIKI.auth.groups).filter(g => groups.includes(g.name)).map(g => g.id)
59-
for (const groupId of _.difference(expectedGroups, currentGroups)) {
60-
await user.$relatedQuery('groups').relate(groupId)
61-
}
62-
for (const groupId of _.difference(currentGroups, expectedGroups)) {
63-
await user.$relatedQuery('groups').unrelate().where('groupId', groupId)
64-
}
72+
for (const groupId of _.difference(currentGroups, expectedGroups)) {
73+
await user.$relatedQuery('groups').unrelate().where('groupId', groupId)
6574
}
6675
}
67-
cb(null, user)
68-
} catch (err) {
69-
cb(err, null)
7076
}
71-
})
72-
)
73-
} catch (error) {
74-
console.error('Error initializing OpenID Connect strategy:', error)
77+
cb(null, user)
78+
} catch (err) {
79+
cb(err, null)
80+
}
81+
}))
82+
} catch (err) {
83+
WIKI.logger.error(`Error initializing OpenID Connect strategy: ${err}`)
7584
}
7685
},
77-
logout (conf) {
86+
logout(conf) {
7887
return conf.logoutURL || '/'
7988
}
8089
}

server/modules/authentication/oidc/definition.yml

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,65 +27,72 @@ props:
2727
title: Well-Known Configuration URL
2828
hint: The Well-Known configuration Endpoint URL (e.g. https://provider/.well-known/openid-configuration)
2929
order: 3
30+
authorizationURL:
31+
type: String
32+
title: Authorization Endpoint URL
33+
hint: Application Authorization Endpoint URL (overrides value from well-known URL if set)
34+
order: 4
35+
tokenURL:
36+
type: String
37+
title: Token Endpoint URL
38+
hint: Application Token Endpoint URL (overrides value from well-known URL if set)
39+
order: 5
40+
userInfoURL:
41+
type: String
42+
title: User Info Endpoint URL
43+
hint: User Info Endpoint URL (overrides value from well-known URL if set)
44+
order: 6
3045
skipUserProfile:
3146
type: Boolean
3247
default: false
3348
title: Skip User Profile
3449
hint: Skips call to the OIDC UserInfo endpoint
35-
order: 4
36-
userIdClaim:
37-
userIdClaim:
50+
order: 7
51+
issuer:
3852
type: String
39-
title: ID Claim
40-
hint: Field containing the user ID
41-
default: id
42-
maxWidth: 500
43-
order: 5
53+
title: Issuer
54+
hint: Issuer URL (overrides value from well-known URL if set)
55+
order: 8
4456
emailClaim:
4557
type: String
4658
title: Email Claim
4759
hint: Field containing the email address
4860
default: email
4961
maxWidth: 500
50-
order: 6
62+
order: 9
5163
displayNameClaim:
5264
type: String
5365
title: Display Name Claim
5466
hint: Field containing the user display name
5567
default: displayName
5668
maxWidth: 500
57-
order: 7
69+
order: 10
5870
mergeIdTokenClaims:
5971
type: Boolean
6072
title: Merge ID Token Claims
6173
hint: If enabled, verifies the ID token and merges its claims into the user profile
6274
default: false
63-
order: 8
75+
order: 11
6476
mapGroups:
6577
type: Boolean
6678
title: Map Groups
6779
hint: Map groups matching names from the groups claim value
6880
default: false
69-
order: 9
81+
order: 12
7082
groupsClaim:
7183
type: String
7284
title: Groups Claim
7385
hint: Field containing the group names
7486
default: groups
7587
maxWidth: 500
76-
order: 10
88+
order: 13
7789
logoutURL:
7890
type: String
7991
title: Logout URL
8092
hint: (optional) Logout URL on the OAuth2 provider where the user will be redirected to complete the logout process.
81-
order: 11
82-
scope:
83-
type: String
84-
title: Scope
85-
hint: (optional) Application Client permission scopes.
86-
order: 12
93+
order: 14
8794
acrValues:
8895
type: String
8996
title: ACR Values
9097
hint: (optional) Authentication Context Class Reference
91-
order: 13
98+
order: 15

0 commit comments

Comments
 (0)