33 *
44 * Copyright (C) 2020 Nginx, Inc.
55 */
6- var newSession = false ; // Used by oidcAuth() and validateIdToken()
7-
86export default { auth, codeExchange, validateIdToken, logout} ;
97
108function retryOriginalRequest ( r ) {
@@ -32,8 +30,6 @@ function auth(r, afterSyncCheck) {
3230 }
3331
3432 if ( ! r . variables . refresh_token || r . variables . refresh_token == "-" ) {
35- newSession = true ;
36-
3733 // Check we have all necessary configuration variables (referenced only by njs)
3834 var oidcConfigurables = [ "authz_endpoint" , "scopes" , "hmac_key" , "cookie_flags" ] ;
3935 var missingConfig = [ ] ;
@@ -54,7 +50,7 @@ function auth(r, afterSyncCheck) {
5450
5551 // Pass the refresh token to the /_refresh location so that it can be
5652 // 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" ) ,
5854 function ( reply ) {
5955 if ( reply . status != 200 ) {
6056 // Refresh request failed, log the reason
@@ -142,7 +138,7 @@ function codeExchange(r) {
142138
143139 // Pass the authorization code to the /_token location so that it can be
144140 // 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 ) {
146142 if ( reply . status == 504 ) {
147143 r . error ( "OIDC timeout connecting to IdP when sending authorization code" ) ;
148144 r . return ( 504 ) ;
@@ -200,7 +196,7 @@ function codeExchange(r) {
200196
201197 r . headersOut [ "Set-Cookie" ] = "auth_token=" + r . variables . request_id + "; " + r . variables . oidc_cookie_flags ;
202198 r . return ( 302 , r . variables . redirect_base + decodeURIComponent ( r . variables . cookie_auth_redir ) ) ;
203- }
199+ }
204200 ) ;
205201 } catch ( e ) {
206202 r . error ( "OIDC authorization code sent but token response is not JSON. " + reply . responseText ) ;
@@ -241,10 +237,9 @@ function validateIdToken(r) {
241237 validToken = false ;
242238 }
243239
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 ) {
248243 var client_nonce_hash = "" ;
249244 if ( r . variables . cookie_auth_nonce ) {
250245 var c = require ( 'crypto' ) ;
@@ -255,6 +250,9 @@ function validateIdToken(r) {
255250 r . error ( "OIDC ID Token validation error: nonce from token (" + r . variables . jwt_claim_nonce + ") does not match client (" + client_nonce_hash + ")" ) ;
256251 validToken = false ;
257252 }
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 ;
258256 }
259257
260258 if ( validToken ) {
@@ -297,7 +295,7 @@ function logout(r) {
297295
298296 // Construct logout arguments for RP-initiated logout
299297 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 ) ;
301299 performLogout ( r . variables . oidc_end_session_endpoint + logoutArgs ) ;
302300 } else {
303301 // Fallback to traditional logout approach
@@ -337,12 +335,38 @@ function getAuthZArgs(r) {
337335 return authZArgs ;
338336}
339337
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 ;
347355 }
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 ;
348372}
0 commit comments