3
3
*
4
4
* Copyright (C) 2020 Nginx, Inc.
5
5
*/
6
- var newSession = false ; // Used by oidcAuth() and validateIdToken()
7
-
8
6
export default { auth, codeExchange, validateIdToken, logout} ;
9
7
10
8
function retryOriginalRequest ( r ) {
@@ -32,8 +30,6 @@ function auth(r, afterSyncCheck) {
32
30
}
33
31
34
32
if ( ! r . variables . refresh_token || r . variables . refresh_token == "-" ) {
35
- newSession = true ;
36
-
37
33
// Check we have all necessary configuration variables (referenced only by njs)
38
34
var oidcConfigurables = [ "authz_endpoint" , "scopes" , "hmac_key" , "cookie_flags" ] ;
39
35
var missingConfig = [ ] ;
@@ -54,7 +50,7 @@ function auth(r, afterSyncCheck) {
54
50
55
51
// Pass the refresh token to the /_refresh location so that it can be
56
52
// proxied to the IdP in exchange for a new id_token
57
- r . subrequest ( "/_refresh" , "token=" + r . variables . refresh_token ,
53
+ r . subrequest ( "/_refresh" , generateTokenRequestParams ( r , " refresh_token" ) ,
58
54
function ( reply ) {
59
55
if ( reply . status != 200 ) {
60
56
// Refresh request failed, log the reason
@@ -142,7 +138,7 @@ function codeExchange(r) {
142
138
143
139
// Pass the authorization code to the /_token location so that it can be
144
140
// proxied to the IdP in exchange for a JWT
145
- r . subrequest ( "/_token" , idpClientAuth ( r ) , function ( reply ) {
141
+ r . subrequest ( "/_token" , generateTokenRequestParams ( r , "authorization_code" ) , function ( reply ) {
146
142
if ( reply . status == 504 ) {
147
143
r . error ( "OIDC timeout connecting to IdP when sending authorization code" ) ;
148
144
r . return ( 504 ) ;
@@ -200,7 +196,7 @@ function codeExchange(r) {
200
196
201
197
r . headersOut [ "Set-Cookie" ] = "auth_token=" + r . variables . request_id + "; " + r . variables . oidc_cookie_flags ;
202
198
r . return ( 302 , r . variables . redirect_base + decodeURIComponent ( r . variables . cookie_auth_redir ) ) ;
203
- }
199
+ }
204
200
) ;
205
201
} catch ( e ) {
206
202
r . error ( "OIDC authorization code sent but token response is not JSON. " + reply . responseText ) ;
@@ -241,10 +237,9 @@ function validateIdToken(r) {
241
237
validToken = false ;
242
238
}
243
239
244
- // If we receive a nonce in the ID Token then we will use the auth_nonce cookies
245
- // to check that the JWT can be validated as being directly related to the
246
- // original request by this client. This mitigates against token replay attacks.
247
- if ( newSession ) {
240
+ // According to OIDC Core 1.0 Section 2:
241
+ // "If present in the ID Token, Clients MUST verify that the nonce Claim Value is equal to the value of the nonce parameter sent in the Authentication Request."
242
+ if ( r . variables . jwt_claim_nonce ) {
248
243
var client_nonce_hash = "" ;
249
244
if ( r . variables . cookie_auth_nonce ) {
250
245
var c = require ( 'crypto' ) ;
@@ -255,6 +250,9 @@ function validateIdToken(r) {
255
250
r . error ( "OIDC ID Token validation error: nonce from token (" + r . variables . jwt_claim_nonce + ") does not match client (" + client_nonce_hash + ")" ) ;
256
251
validToken = false ;
257
252
}
253
+ } else if ( ! r . variables . refresh_token || r . variables . refresh_token == "-" ) {
254
+ r . error ( "OIDC ID Token validation error: missing nonce claim in ID Token during initial authentication." ) ;
255
+ validToken = false ;
258
256
}
259
257
260
258
if ( validToken ) {
@@ -297,7 +295,7 @@ function logout(r) {
297
295
298
296
// Construct logout arguments for RP-initiated logout
299
297
var logoutArgs = "?post_logout_redirect_uri=" + encodeURIComponent ( logoutRedirectUrl ) +
300
- "&id_token_hint=" + encodeURIComponent ( r . variables . session_jwt ) ;
298
+ "&id_token_hint=" + encodeURIComponent ( r . variables . session_jwt ) ;
301
299
performLogout ( r . variables . oidc_end_session_endpoint + logoutArgs ) ;
302
300
} else {
303
301
// Fallback to traditional logout approach
@@ -337,12 +335,38 @@ function getAuthZArgs(r) {
337
335
return authZArgs ;
338
336
}
339
337
340
- function idpClientAuth ( r ) {
341
- // If PKCE is enabled we have to use the code_verifier
342
- if ( r . variables . oidc_pkce_enable == 1 ) {
343
- r . variables . pkce_id = r . variables . arg_state ;
344
- return "code=" + r . variables . arg_code + "&code_verifier=" + r . variables . pkce_code_verifier ;
345
- } else {
346
- return "code=" + r . variables . arg_code + "&client_secret=" + r . variables . oidc_client_secret ;
338
+ function generateTokenRequestParams ( r , grant_type ) {
339
+ var body = "grant_type=" + grant_type + "&client_id=" + r . variables . oidc_client ;
340
+
341
+ switch ( grant_type ) {
342
+ case "authorization_code" :
343
+ body += "&code=" + r . variables . arg_code + "&redirect_uri=" + r . variables . redirect_base + r . variables . redir_location ;
344
+ if ( r . variables . oidc_pkce_enable == 1 ) {
345
+ r . variables . pkce_id = r . variables . arg_state ;
346
+ body += "&code_verifier=" + r . variables . pkce_code_verifier ;
347
+ }
348
+ break ;
349
+ case "refresh_token" :
350
+ body += "&refresh_token=" + r . variables . refresh_token ;
351
+ break ;
352
+ default :
353
+ r . error ( "Unsupported grant type: " + grant_type ) ;
354
+ return ;
347
355
}
356
+
357
+ var options = {
358
+ body : body ,
359
+ method : "POST"
360
+ } ;
361
+
362
+ if ( r . variables . oidc_pkce_enable != 1 ) {
363
+ if ( r . variables . oidc_client_auth_method === "client_secret_basic" ) {
364
+ let auth_basic = "Basic " + Buffer . from ( r . variables . oidc_client + ":" + r . variables . oidc_client_secret ) . toString ( 'base64' ) ;
365
+ options . args = "secret_basic=" + auth_basic ;
366
+ } else {
367
+ options . body += "&client_secret=" + r . variables . oidc_client_secret ;
368
+ }
369
+ }
370
+
371
+ return options ;
348
372
}
0 commit comments