1717import org .jose4j .lang .UnresolvableKeyException ;
1818
1919import io .quarkus .oidc .AccessTokenCredential ;
20+ import io .quarkus .oidc .AuthorizationCodeTokens ;
2021import io .quarkus .oidc .IdTokenCredential ;
2122import io .quarkus .oidc .OIDCException ;
2223import io .quarkus .oidc .OidcTenantConfig ;
@@ -199,7 +200,7 @@ private Uni<TokenVerificationResult> verifyPrimaryTokenUni(Map<String, Object> r
199200 } else {
200201 final boolean idToken = isIdToken (request );
201202 Uni <TokenVerificationResult > result = verifyTokenUni (requestData , resolvedContext , request .getToken (), idToken ,
202- userInfo );
203+ false , userInfo );
203204 if (!idToken && resolvedContext .oidcConfig ().token ().binding ().certificate ()) {
204205 return result .onItem ().transform (new Function <TokenVerificationResult , TokenVerificationResult >() {
205206
@@ -269,7 +270,7 @@ public Uni<SecurityIdentity> apply(TokenVerificationResult codeAccessTokenResult
269270 }
270271 if (codeAccessTokenResult != null ) {
271272 if (tokenAutoRefreshPrepared (codeAccessTokenResult , requestData ,
272- resolvedContext .oidcConfig ())) {
273+ resolvedContext .oidcConfig (), true )) {
273274 return Uni .createFrom ().failure (new TokenAutoRefreshException (null ));
274275 }
275276 requestData .put (OidcUtils .CODE_ACCESS_TOKEN_RESULT , codeAccessTokenResult );
@@ -346,7 +347,7 @@ private Uni<SecurityIdentity> createSecurityIdentityWithOidcServer(TokenVerifica
346347 // If the primary token is a bearer access token then there's no point of checking if
347348 // it should be refreshed as RT is only available for the code flow tokens
348349 if (isIdToken (request )
349- && tokenAutoRefreshPrepared (result , requestData , resolvedContext .oidcConfig ())) {
350+ && tokenAutoRefreshPrepared (result , requestData , resolvedContext .oidcConfig (), false )) {
350351 return Uni .createFrom ().failure (new TokenAutoRefreshException (securityIdentity ));
351352 } else {
352353 return Uni .createFrom ().item (securityIdentity );
@@ -412,7 +413,7 @@ public String getName() {
412413 // If the primary token is a bearer access token then there's no point of checking if
413414 // it should be refreshed as RT is only available for the code flow tokens
414415 if (isIdToken (request )
415- && tokenAutoRefreshPrepared (result , requestData , resolvedContext .oidcConfig ())) {
416+ && tokenAutoRefreshPrepared (result , requestData , resolvedContext .oidcConfig (), false )) {
416417 return Uni .createFrom ().failure (new TokenAutoRefreshException (identity ));
417418 }
418419 return Uni .createFrom ().item (identity );
@@ -429,7 +430,7 @@ private static boolean isIdToken(TokenAuthenticationRequest request) {
429430 }
430431
431432 private static boolean tokenAutoRefreshPrepared (TokenVerificationResult result , Map <String , Object > requestData ,
432- OidcTenantConfig oidcConfig ) {
433+ OidcTenantConfig oidcConfig , boolean codeFlowAccessToken ) {
433434 if (result != null && oidcConfig .token ().refreshExpired ()
434435 && oidcConfig .token ().refreshTokenTimeSkew ().isPresent ()
435436 && requestData .get (REFRESH_TOKEN_GRANT_RESPONSE ) != Boolean .TRUE
@@ -440,9 +441,18 @@ private static boolean tokenAutoRefreshPrepared(TokenVerificationResult result,
440441 } else if (result .introspectionResult != null ) {
441442 expiry = result .introspectionResult .getLong (OidcConstants .INTROSPECTION_TOKEN_EXP );
442443 }
444+ final long now = System .currentTimeMillis () / 1000 ;
445+ if (expiry == null && codeFlowAccessToken ) {
446+ // JWT or introspection response `exp` property has a number of seconds since epoch.
447+ // The code flow access token `expires_in` property is relative to the current time.
448+ Long expiresIn = ((AuthorizationCodeTokens ) requestData .get (AuthorizationCodeTokens .class .getName ()))
449+ .getAccessTokenExpiresIn ();
450+ if (expiresIn != null ) {
451+ expiry = now + expiresIn ;
452+ }
453+ }
443454 if (expiry != null ) {
444455 final long refreshTokenTimeSkew = oidcConfig .token ().refreshTokenTimeSkew ().get ().getSeconds ();
445- final long now = System .currentTimeMillis () / 1000 ;
446456 return now + refreshTokenTimeSkew > expiry ;
447457 }
448458 }
@@ -478,15 +488,21 @@ private Uni<TokenVerificationResult> verifyCodeFlowAccessTokenUni(Map<String, Ob
478488 && (resolvedContext .oidcConfig ().authentication ().verifyAccessToken ()
479489 || resolvedContext .oidcConfig ().roles ().source ().orElse (null ) == Source .accesstoken )) {
480490 final String codeAccessToken = (String ) requestData .get (OidcConstants .ACCESS_TOKEN_VALUE );
481- return verifyTokenUni (requestData , resolvedContext , new AccessTokenCredential (codeAccessToken ), false , userInfo );
491+ return verifyTokenUni (requestData , resolvedContext , new AccessTokenCredential (codeAccessToken ), false , true ,
492+ userInfo );
482493 } else {
483494 return NULL_CODE_ACCESS_TOKEN_UNI ;
484495 }
485496 }
486497
487498 private Uni <TokenVerificationResult > verifyTokenUni (Map <String , Object > requestData , TenantConfigContext resolvedContext ,
488- TokenCredential tokenCred , boolean enforceAudienceVerification , UserInfo userInfo ) {
499+ TokenCredential tokenCred , boolean enforceAudienceVerification , boolean codeFlowAccessToken , UserInfo userInfo ) {
489500 final String token = tokenCred .getToken ();
501+ Long expiresIn = null ;
502+ if (codeFlowAccessToken ) {
503+ expiresIn = ((AuthorizationCodeTokens ) requestData .get (AuthorizationCodeTokens .class .getName ()))
504+ .getAccessTokenExpiresIn ();
505+ }
490506 if (OidcUtils .isOpaqueToken (token )) {
491507 if (!resolvedContext .oidcConfig ().token ().allowOpaqueTokenIntrospection ()) {
492508 LOG .debug ("Token is opaque but the opaque token introspection is not allowed" );
@@ -504,12 +520,12 @@ private Uni<TokenVerificationResult> verifyTokenUni(Map<String, Object> requestD
504520 }
505521 }
506522 LOG .debug ("Starting the opaque token introspection" );
507- return introspectTokenUni (resolvedContext , token , false );
523+ return introspectTokenUni (resolvedContext , token , expiresIn , false );
508524 } else if (resolvedContext .provider ().getMetadata ().getJsonWebKeySetUri () == null
509525 || resolvedContext .oidcConfig ().token ().requireJwtIntrospectionOnly ()) {
510526 // Verify JWT token with the remote introspection
511527 LOG .debug ("Starting the JWT token introspection" );
512- return introspectTokenUni (resolvedContext , token , false );
528+ return introspectTokenUni (resolvedContext , token , expiresIn , false );
513529 } else if (resolvedContext .oidcConfig ().jwks ().resolveEarly ()) {
514530 // Verify JWT token with the local JWK keys with a possible remote introspection fallback
515531 final String nonce = tokenCred instanceof IdTokenCredential ? (String ) requestData .get (OidcConstants .NONCE ) : null ;
@@ -522,7 +538,7 @@ private Uni<TokenVerificationResult> verifyTokenUni(Map<String, Object> requestD
522538 if (t .getCause () instanceof UnresolvableKeyException ) {
523539 LOG .debug ("No matching JWK key is found, refreshing and repeating the token verification" );
524540 return refreshJwksAndVerifyTokenUni (resolvedContext , token , enforceAudienceVerification ,
525- resolvedContext .oidcConfig ().token ().subjectRequired (), nonce );
541+ resolvedContext .oidcConfig ().token ().subjectRequired (), nonce , expiresIn );
526542 } else {
527543 LOG .debugf ("Token verification has failed: %s" , t .getMessage ());
528544 return Uni .createFrom ().failure (t );
@@ -531,7 +547,7 @@ private Uni<TokenVerificationResult> verifyTokenUni(Map<String, Object> requestD
531547 } else {
532548 final String nonce = (String ) requestData .get (OidcConstants .NONCE );
533549 return resolveJwksAndVerifyTokenUni (resolvedContext , tokenCred , enforceAudienceVerification ,
534- resolvedContext .oidcConfig ().token ().subjectRequired (), nonce );
550+ resolvedContext .oidcConfig ().token ().subjectRequired (), nonce , expiresIn );
535551 }
536552 }
537553
@@ -545,21 +561,21 @@ private Uni<TokenVerificationResult> verifySelfSignedTokenUni(TenantConfigContex
545561 }
546562
547563 private Uni <TokenVerificationResult > refreshJwksAndVerifyTokenUni (TenantConfigContext resolvedContext , String token ,
548- boolean enforceAudienceVerification , boolean subjectRequired , String nonce ) {
564+ boolean enforceAudienceVerification , boolean subjectRequired , String nonce , Long expiresIn ) {
549565 return resolvedContext .provider ()
550566 .refreshJwksAndVerifyJwtToken (token , enforceAudienceVerification , subjectRequired , nonce )
551567 .onFailure (f -> fallbackToIntrospectionIfNoMatchingKey (f , resolvedContext ))
552- .recoverWithUni (f -> introspectTokenUni (resolvedContext , token , true ));
568+ .recoverWithUni (f -> introspectTokenUni (resolvedContext , token , expiresIn , true ));
553569 }
554570
555571 private Uni <TokenVerificationResult > resolveJwksAndVerifyTokenUni (TenantConfigContext resolvedContext ,
556572 TokenCredential tokenCred ,
557- boolean enforceAudienceVerification , boolean subjectRequired , String nonce ) {
573+ boolean enforceAudienceVerification , boolean subjectRequired , String nonce , Long expiresIn ) {
558574 return resolvedContext .provider ()
559575 .getKeyResolverAndVerifyJwtToken (tokenCred , enforceAudienceVerification , subjectRequired , nonce ,
560576 (tokenCred instanceof IdTokenCredential ))
561577 .onFailure (f -> fallbackToIntrospectionIfNoMatchingKey (f , resolvedContext ))
562- .recoverWithUni (f -> introspectTokenUni (resolvedContext , tokenCred .getToken (), true ));
578+ .recoverWithUni (f -> introspectTokenUni (resolvedContext , tokenCred .getToken (), expiresIn , true ));
563579 }
564580
565581 private static boolean fallbackToIntrospectionIfNoMatchingKey (Throwable f , TenantConfigContext resolvedContext ) {
@@ -577,28 +593,29 @@ private static boolean fallbackToIntrospectionIfNoMatchingKey(Throwable f, Tenan
577593 }
578594
579595 private Uni <TokenVerificationResult > introspectTokenUni (TenantConfigContext resolvedContext , final String token ,
580- boolean fallbackFromJwkMatch ) {
596+ Long expiresIn , boolean fallbackFromJwkMatch ) {
581597 TokenIntrospectionCache tokenIntrospectionCache = tenantResolver .getTokenIntrospectionCache ();
582598 Uni <TokenIntrospection > tokenIntrospectionUni = tokenIntrospectionCache == null ? null
583599 : tokenIntrospectionCache
584600 .getIntrospection (token , resolvedContext .oidcConfig (), getIntrospectionRequestContext );
585601 if (tokenIntrospectionUni == null ) {
586- tokenIntrospectionUni = newTokenIntrospectionUni (resolvedContext , token , fallbackFromJwkMatch );
602+ tokenIntrospectionUni = newTokenIntrospectionUni (resolvedContext , token , expiresIn , fallbackFromJwkMatch );
587603 } else {
588604 tokenIntrospectionUni = tokenIntrospectionUni .onItem ().ifNull ()
589605 .switchTo (new Supplier <Uni <? extends TokenIntrospection >>() {
590606 @ Override
591607 public Uni <TokenIntrospection > get () {
592- return newTokenIntrospectionUni (resolvedContext , token , fallbackFromJwkMatch );
608+ return newTokenIntrospectionUni (resolvedContext , token , expiresIn , fallbackFromJwkMatch );
593609 }
594610 });
595611 }
596612 return tokenIntrospectionUni .onItem ().transform (t -> new TokenVerificationResult (null , t ));
597613 }
598614
599615 private Uni <TokenIntrospection > newTokenIntrospectionUni (TenantConfigContext resolvedContext , String token ,
600- boolean fallbackFromJwkMatch ) {
601- Uni <TokenIntrospection > tokenIntrospectionUni = resolvedContext .provider ().introspectToken (token , fallbackFromJwkMatch );
616+ Long expiresIn , boolean fallbackFromJwkMatch ) {
617+ Uni <TokenIntrospection > tokenIntrospectionUni = resolvedContext .provider ().introspectToken (token , expiresIn ,
618+ fallbackFromJwkMatch );
602619 if (tenantResolver .getTokenIntrospectionCache () == null
603620 || !resolvedContext .oidcConfig ().allowTokenIntrospectionCache ()) {
604621 return tokenIntrospectionUni ;
0 commit comments