This is an automated email from the ASF dual-hosted git repository.

thenatog pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new bf962f6  NIFI-7584 Added OIDC logout mechanism. Added method to 
validate the OIDC Access Token for the revoke endpoint. Created a new callback 
URI of oidc/logoutCallback to handle certain OIDC logout cases. Changed method 
to exchange the Authorization Code for a Login Authentication Token. Added a 
new method to exchange the AuthN Code for an Access Token. Changed method to 
convert OIDC Token to a Login AuthN Token instead of a NiFi JWT. Created new 
OidcServiceGroovyTest class.
bf962f6 is described below

commit bf962f62275071dfe5f00aedd028b8231680450c
Author: mtien <[email protected]>
AuthorDate: Mon Sep 14 18:43:43 2020 -0700

    NIFI-7584 Added OIDC logout mechanism.
    Added method to validate the OIDC Access Token for the revoke endpoint.
    Created a new callback URI of oidc/logoutCallback to handle certain OIDC 
logout cases.
    Changed method to exchange the Authorization Code for a Login 
Authentication Token.
    Added a new method to exchange the AuthN Code for an Access Token.
    Changed method to convert OIDC Token to a Login AuthN Token instead of a 
NiFi JWT.
    Created new OidcServiceGroovyTest class.
    
    NIFI-7584-rebase Added test.
    
    NIFI-7584 Fixed a checkstyle issue.
    
    NIFI-7584 Removed a dependency not in use.
    
    NIFI-7584 Made revisions based on PR review.
    Refactored revoke endpoint POST request to a private method.
    Removed unnecessary dependencies.
    Fixed Regex Pattern to search for literal dot character.
    Fixed logging the Exception message.
    Fixed caught Exception.
    Changed timeout value to a static variable.
    Changed repeating error messages to a static string.
    Reduced sleep duration in unit test.
    Refactored cookie generation to private method.
    
    NIFI-7584 Fixed the snapshot version.
    
    Signed-off-by: Nathan Gough <[email protected]>
    
    This closes #4593.
---
 .../nifi-framework/nifi-web/nifi-web-api/pom.xml   |   4 +
 .../nifi/web/NiFiWebApiSecurityConfiguration.java  |   5 +-
 .../org/apache/nifi/web/api/AccessResource.java    | 387 ++++++++++++++++++---
 .../web/security/oidc/OidcIdentityProvider.java    |  34 +-
 .../apache/nifi/web/security/oidc/OidcService.java |  78 ++++-
 .../oidc/StandardOidcIdentityProvider.java         | 192 ++++++++--
 .../web/security/oidc/OidcServiceGroovyTest.groovy | 214 ++++++++++++
 .../StandardOidcIdentityProviderGroovyTest.groovy  | 379 +++++++++++++++-----
 .../nifi/web/security/oidc/OidcServiceTest.java    |  73 ++--
 9 files changed, 1130 insertions(+), 236 deletions(-)

diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml
index 51b665f..6803f0e 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml
@@ -422,5 +422,9 @@
             <version>1.3</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
     </dependencies>
 </project>
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
index b8d35e9..7aee52b 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java
@@ -16,6 +16,7 @@
  */
 package org.apache.nifi.web;
 
+import java.util.Arrays;
 import org.apache.nifi.util.NiFiProperties;
 import 
org.apache.nifi.web.security.anonymous.NiFiAnonymousAuthenticationFilter;
 import 
org.apache.nifi.web.security.anonymous.NiFiAnonymousAuthenticationProvider;
@@ -48,8 +49,6 @@ import org.springframework.web.cors.CorsConfiguration;
 import org.springframework.web.cors.CorsConfigurationSource;
 import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 
-import java.util.Arrays;
-
 /**
  * NiFi Web Api Spring security. Applies the various NiFiAuthenticationFilter 
servlet filters which will extract authentication
  * credentials from API requests.
@@ -92,7 +91,7 @@ public class NiFiWebApiSecurityConfiguration extends 
WebSecurityConfigurerAdapte
         webSecurity
                 .ignoring()
                     .antMatchers("/access", "/access/config", "/access/token", 
"/access/kerberos",
-                            "/access/oidc/exchange", "/access/oidc/callback", 
"/access/oidc/request",
+                            "/access/oidc/exchange", "/access/oidc/callback", 
"/access/oidc/logoutCallback", "/access/oidc/request",
                             "/access/knox/callback", "/access/knox/request");
     }
 
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
index 9a3eec2..32e1b22 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
@@ -20,6 +20,7 @@ import com.nimbusds.oauth2.sdk.AuthorizationCode;
 import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
 import com.nimbusds.oauth2.sdk.AuthorizationGrant;
 import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.http.HTTPResponse;
 import com.nimbusds.oauth2.sdk.id.State;
 import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
 import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
@@ -29,10 +30,15 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiResponse;
 import io.swagger.annotations.ApiResponses;
+import java.io.IOException;
 import java.net.URI;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import javax.servlet.ServletContext;
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
@@ -49,6 +55,14 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.message.BasicNameValuePair;
 import org.apache.nifi.admin.service.AdministrationException;
 import org.apache.nifi.authentication.AuthenticationResponse;
 import org.apache.nifi.authentication.LoginCredentials;
@@ -104,6 +118,14 @@ public class AccessResource extends ApplicationResource {
 
     private static final String OIDC_REQUEST_IDENTIFIER = 
"oidc-request-identifier";
     private static final String OIDC_ERROR_TITLE = "Unable to continue login 
sequence";
+    private static final String OIDC_ID_TOKEN_AUTHN_ERROR = "Unable to 
exchange authorization for ID token: ";
+    private static final String OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED_MSG 
= "OpenId Connect support is not configured";
+    private static final String REVOKE_ACCESS_TOKEN_LOGOUT = 
"oidc_access_token_logout";
+    private static final String ID_TOKEN_LOGOUT = "oidc_id_token_logout";
+    private static final String STANDARD_LOGOUT = "oidc_standard_logout";
+    private static final Pattern REVOKE_ACCESS_TOKEN_LOGOUT_FORMAT = 
Pattern.compile("(\\.google\\.com)");
+    private static final Pattern ID_TOKEN_LOGOUT_FORMAT = 
Pattern.compile("(\\.okta)");
+    private static final int msTimeout = 30_000;
 
     private static final String AUTHENTICATION_NOT_ENABLED_MSG = "User 
authentication/authorization is only supported when running over HTTPS.";
 
@@ -166,34 +188,15 @@ public class AccessResource extends ApplicationResource {
 
         // ensure oidc is enabled
         if (!oidcService.isOidcEnabled()) {
-            forwardToMessagePage(httpServletRequest, httpServletResponse, 
"OpenId Connect is not configured.");
+            forwardToMessagePage(httpServletRequest, httpServletResponse, 
OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED_MSG);
             return;
         }
 
-        final String oidcRequestIdentifier = UUID.randomUUID().toString();
-
-        // generate a cookie to associate this login sequence
-        final Cookie cookie = new Cookie(OIDC_REQUEST_IDENTIFIER, 
oidcRequestIdentifier);
-        cookie.setPath("/");
-        cookie.setHttpOnly(true);
-        cookie.setMaxAge(60);
-        cookie.setSecure(true);
-        httpServletResponse.addCookie(cookie);
-
-        // get the state for this request
-        final State state = oidcService.createState(oidcRequestIdentifier);
-
-        // build the authorization uri
-        final URI authorizationUri = 
UriBuilder.fromUri(oidcService.getAuthorizationEndpoint())
-                .queryParam("client_id", oidcService.getClientId())
-                .queryParam("response_type", "code")
-                .queryParam("scope", oidcService.getScope().toString())
-                .queryParam("state", state.getValue())
-                .queryParam("redirect_uri", getOidcCallback())
-                .build();
+        // generate the authorization uri
+        URI authorizationURI = 
oidcRequestAuthorizationCode(httpServletResponse, getOidcCallback());
 
         // generate the response
-        httpServletResponse.sendRedirect(authorizationUri.toString());
+        httpServletResponse.sendRedirect(authorizationURI.toString());
     }
 
     @GET
@@ -207,19 +210,20 @@ public class AccessResource extends ApplicationResource {
     public void oidcCallback(@Context HttpServletRequest httpServletRequest, 
@Context HttpServletResponse httpServletResponse) throws Exception {
         // only consider user specific access over https
         if (!httpServletRequest.isSecure()) {
-            forwardToMessagePage(httpServletRequest, httpServletResponse, 
"User authentication/authorization is only supported when running over HTTPS.");
+            forwardToMessagePage(httpServletRequest, httpServletResponse, 
AUTHENTICATION_NOT_ENABLED_MSG);
             return;
         }
 
         // ensure oidc is enabled
         if (!oidcService.isOidcEnabled()) {
-            forwardToMessagePage(httpServletRequest, httpServletResponse, 
"OpenId Connect is not configured.");
+            forwardToMessagePage(httpServletRequest, httpServletResponse, 
OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED_MSG);
             return;
         }
 
         final String oidcRequestIdentifier = 
getCookieValue(httpServletRequest.getCookies(), OIDC_REQUEST_IDENTIFIER);
         if (oidcRequestIdentifier == null) {
-            forwardToMessagePage(httpServletRequest, httpServletResponse, "The 
login request identifier was not found in the request. Unable to continue.");
+            forwardToMessagePage(httpServletRequest, httpServletResponse, "The 
login request identifier was " +
+                    "not found in the request. Unable to continue.");
             return;
         }
 
@@ -233,7 +237,8 @@ public class AccessResource extends ApplicationResource {
             removeOidcRequestCookie(httpServletResponse);
 
             // forward to the error page
-            forwardToMessagePage(httpServletRequest, httpServletResponse, 
"Unable to parse the redirect URI from the OpenId Connect Provider. Unable to 
continue login process.");
+            forwardToMessagePage(httpServletRequest, httpServletResponse, 
"Unable to parse the redirect URI " +
+                    "from the OpenId Connect Provider. Unable to continue 
login process.");
             return;
         }
 
@@ -243,13 +248,15 @@ public class AccessResource extends ApplicationResource {
             // confirm state
             final State state = successfulOidcResponse.getState();
             if (state == null || 
!oidcService.isStateValid(oidcRequestIdentifier, state)) {
-                logger.error("The state value returned by the OpenId Connect 
Provider does not match the stored state. Unable to continue login process.");
+                logger.error("The state value returned by the OpenId Connect 
Provider does not match the stored state. " +
+                        "Unable to continue login process.");
 
                 // remove the oidc request cookie
                 removeOidcRequestCookie(httpServletResponse);
 
                 // forward to the error page
-                forwardToMessagePage(httpServletRequest, httpServletResponse, 
"Purposed state does not match the stored state. Unable to continue login 
process.");
+                forwardToMessagePage(httpServletRequest, httpServletResponse, 
"Purposed state does not match " +
+                        "the stored state. Unable to continue login process.");
                 return;
             }
 
@@ -257,15 +264,24 @@ public class AccessResource extends ApplicationResource {
                 // exchange authorization code for id token
                 final AuthorizationCode authorizationCode = 
successfulOidcResponse.getAuthorizationCode();
                 final AuthorizationGrant authorizationGrant = new 
AuthorizationCodeGrant(authorizationCode, URI.create(getOidcCallback()));
-                oidcService.exchangeAuthorizationCode(oidcRequestIdentifier, 
authorizationGrant);
+
+                // get the oidc token
+                LoginAuthenticationToken oidcToken = 
oidcService.exchangeAuthorizationCodeForLoginAuthenticationToken(authorizationGrant);
+
+                // exchange the oidc token for the NiFi token
+                String nifiJwt = jwtService.generateSignedToken(oidcToken);
+
+                // store the NiFi token
+                oidcService.storeJwt(oidcRequestIdentifier, nifiJwt);
+
             } catch (final Exception e) {
-                logger.error("Unable to exchange authorization for ID token: " 
+ e.getMessage(), e);
+                logger.error(OIDC_ID_TOKEN_AUTHN_ERROR + e.getMessage(), e);
 
                 // remove the oidc request cookie
                 removeOidcRequestCookie(httpServletResponse);
 
                 // forward to the error page
-                forwardToMessagePage(httpServletRequest, httpServletResponse, 
"Unable to exchange authorization for ID token: " + e.getMessage());
+                forwardToMessagePage(httpServletRequest, httpServletResponse, 
OIDC_ID_TOKEN_AUTHN_ERROR + e.getMessage());
                 return;
             }
 
@@ -277,7 +293,8 @@ public class AccessResource extends ApplicationResource {
 
             // report the unsuccessful login
             final AuthenticationErrorResponse errorOidcResponse = 
(AuthenticationErrorResponse) oidcResponse;
-            forwardToMessagePage(httpServletRequest, httpServletResponse, 
"Unsuccessful login attempt: " + 
errorOidcResponse.getErrorObject().getDescription());
+            forwardToMessagePage(httpServletRequest, httpServletResponse, 
"Unsuccessful login attempt: "
+                    + errorOidcResponse.getErrorObject().getDescription());
         }
     }
 
@@ -290,7 +307,7 @@ public class AccessResource extends ApplicationResource {
             response = String.class,
             notes = NON_GUARANTEED_ENDPOINT
     )
-    public Response oidcExchange(@Context HttpServletRequest 
httpServletRequest, @Context HttpServletResponse httpServletResponse) throws 
Exception {
+    public Response oidcExchange(@Context HttpServletRequest 
httpServletRequest, @Context HttpServletResponse httpServletResponse) {
         // only consider user specific access over https
         if (!httpServletRequest.isSecure()) {
             throw new 
AuthenticationNotSupportedException(AUTHENTICATION_NOT_ENABLED_MSG);
@@ -298,7 +315,7 @@ public class AccessResource extends ApplicationResource {
 
         // ensure oidc is enabled
         if (!oidcService.isOidcEnabled()) {
-            throw new IllegalStateException("OpenId Connect is not 
configured.");
+            throw new 
IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED_MSG);
         }
 
         final String oidcRequestIdentifier = 
getCookieValue(httpServletRequest.getCookies(), OIDC_REQUEST_IDENTIFIER);
@@ -329,24 +346,198 @@ public class AccessResource extends ApplicationResource {
     )
     public void oidcLogout(@Context HttpServletRequest httpServletRequest, 
@Context HttpServletResponse httpServletResponse) throws Exception {
         if (!httpServletRequest.isSecure()) {
-            throw new IllegalStateException("User authentication/authorization 
is only supported when running over HTTPS.");
+            throw new IllegalStateException(AUTHENTICATION_NOT_ENABLED_MSG);
+        }
+
+        if (!oidcService.isOidcEnabled()) {
+            throw new 
IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED_MSG);
+        }
+
+        // Get the oidc discovery url
+        String oidcDiscoveryUrl = properties.getOidcDiscoveryUrl();
+
+        // Determine the logout method
+        String logoutMethod = determineLogoutMethod(oidcDiscoveryUrl);
+
+        switch (logoutMethod) {
+            case REVOKE_ACCESS_TOKEN_LOGOUT:
+            case ID_TOKEN_LOGOUT:
+                // Make a request to the IdP
+                URI authorizationURI = 
oidcRequestAuthorizationCode(httpServletResponse, getOidcLogoutCallback());
+                httpServletResponse.sendRedirect(authorizationURI.toString());
+                break;
+            case STANDARD_LOGOUT:
+            default:
+                // Get the OIDC end session endpoint
+                URI endSessionEndpoint = oidcService.getEndSessionEndpoint();
+                String postLogoutRedirectUri = generateResourceUri( "..", 
"nifi", "logout-complete");
+
+                if (endSessionEndpoint == null) {
+                    httpServletResponse.sendRedirect(postLogoutRedirectUri);
+                } else {
+                    URI logoutUri = UriBuilder.fromUri(endSessionEndpoint)
+                            .queryParam("post_logout_redirect_uri", 
postLogoutRedirectUri)
+                            .build();
+                    httpServletResponse.sendRedirect(logoutUri.toString());
+                }
+                break;
+        }
+    }
+
+    @GET
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.WILDCARD)
+    @Path("oidc/logoutCallback")
+    @ApiOperation(
+            value = "Redirect/callback URI for processing the result of the 
OpenId Connect logout sequence.",
+            notes = NON_GUARANTEED_ENDPOINT
+    )
+    public void oidcLogoutCallback(@Context HttpServletRequest 
httpServletRequest, @Context HttpServletResponse httpServletResponse) throws 
Exception {
+        // only consider user specific access over https
+        if (!httpServletRequest.isSecure()) {
+            forwardToMessagePage(httpServletRequest, httpServletResponse, 
AUTHENTICATION_NOT_ENABLED_MSG);
+            return;
         }
 
+        // ensure oidc is enabled
         if (!oidcService.isOidcEnabled()) {
-            throw new IllegalStateException("OpenId Connect is not 
configured.");
+            forwardToMessagePage(httpServletRequest, httpServletResponse, 
OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED_MSG);
+            return;
+        }
+
+        final String oidcRequestIdentifier = 
getCookieValue(httpServletRequest.getCookies(), OIDC_REQUEST_IDENTIFIER);
+        if (oidcRequestIdentifier == null) {
+            forwardToMessagePage(httpServletRequest, httpServletResponse, "The 
login request identifier was " +
+                    "not found in the request. Unable to continue.");
+            return;
         }
 
-        URI endSessionEndpoint = oidcService.getEndSessionEndpoint();
-        String postLogoutRedirectUri = generateResourceUri("..", "nifi", 
"logout-complete");
+        final com.nimbusds.openid.connect.sdk.AuthenticationResponse 
oidcResponse;
+        try {
+            oidcResponse = AuthenticationResponseParser.parse(getRequestUri());
+        } catch (final ParseException e) {
+            logger.error("Unable to parse the redirect URI from the OpenId 
Connect Provider. Unable to continue " +
+                    "logout process: " + e.getMessage(), e);
+
+            // remove the oidc request cookie
+            removeOidcRequestCookie(httpServletResponse);
+
+            // forward to the error page
+            forwardToMessagePage(httpServletRequest, httpServletResponse, 
"Unable to parse the redirect URI " +
+                    "from the OpenId Connect Provider. Unable to continue 
logout process.");
+            return;
+        }
+
+        if (oidcResponse.indicatesSuccess()) {
+            final AuthenticationSuccessResponse successfulOidcResponse = 
(AuthenticationSuccessResponse) oidcResponse;
+
+            // confirm state
+            final State state = successfulOidcResponse.getState();
+            if (state == null || 
!oidcService.isStateValid(oidcRequestIdentifier, state)) {
+                logger.error("The state value returned by the OpenId Connect 
Provider does not match the stored " +
+                        "state. Unable to continue login process.");
+
+                // remove the oidc request cookie
+                removeOidcRequestCookie(httpServletResponse);
+
+                // forward to the error page
+                forwardToMessagePage(httpServletRequest, httpServletResponse, 
"Purposed state does not match " +
+                        "the stored state. Unable to continue login process.");
+                return;
+            }
 
-        if (endSessionEndpoint == null) {
-            // handle the case, where the OpenID Provider does not have an end 
session endpoint
-            httpServletResponse.sendRedirect(postLogoutRedirectUri);
+            // Get the oidc discovery url
+            String oidcDiscoveryUrl = properties.getOidcDiscoveryUrl();
+
+            // Determine which logout method to use
+            String logoutMethod = determineLogoutMethod(oidcDiscoveryUrl);
+
+            // Get the authorization code and grant
+            final AuthorizationCode authorizationCode = 
successfulOidcResponse.getAuthorizationCode();
+            final AuthorizationGrant authorizationGrant = new 
AuthorizationCodeGrant(authorizationCode, URI.create(getOidcLogoutCallback()));
+
+            switch (logoutMethod) {
+                case REVOKE_ACCESS_TOKEN_LOGOUT:
+                    // Use the Revocation endpoint + access token
+                    final String accessToken;
+                    try {
+                        // Return the access token
+                        accessToken = 
oidcService.exchangeAuthorizationCodeForAccessToken(authorizationGrant);
+                    } catch (final Exception e) {
+                        logger.error("Unable to exchange authorization for the 
Access token: " + e.getMessage(), e);
+
+                        // Remove the oidc request cookie
+                        removeOidcRequestCookie(httpServletResponse);
+
+                        // Forward to the error page
+                        forwardToMessagePage(httpServletRequest, 
httpServletResponse, OIDC_ID_TOKEN_AUTHN_ERROR + e.getMessage());
+                        return;
+                    }
+
+                    // Build the revoke URI and send the POST request
+                    URI revokeEndpoint = getRevokeEndpoint();
+
+                    if (revokeEndpoint != null) {
+                        try {
+                            // Logout with the revoke endpoint
+                            revokeEndpointRequest(httpServletResponse, 
accessToken, revokeEndpoint);
+
+                        } catch (final IOException e) {
+                            logger.error("There was an error logging out of 
the OpenId Connect Provider: "
+                                    + e.getMessage(), e);
+
+                            // Remove the oidc request cookie
+                            removeOidcRequestCookie(httpServletResponse);
+
+                            // Forward to the error page
+                            forwardToMessagePage(httpServletRequest, 
httpServletResponse,
+                                    "There was an error logging out of the 
OpenId Connect Provider: "
+                                            + e.getMessage());
+                        }
+                    }
+                    break;
+                case ID_TOKEN_LOGOUT:
+                    // Use the end session endpoint + ID Token
+                    final String idToken;
+                    try {
+                        // Return the ID Token
+                        idToken = 
oidcService.exchangeAuthorizationCodeForIdToken(authorizationGrant);
+                    } catch (final Exception e) {
+                        logger.error(OIDC_ID_TOKEN_AUTHN_ERROR + 
e.getMessage(), e);
+
+                        // Remove the oidc request cookie
+                        removeOidcRequestCookie(httpServletResponse);
+
+                        // Forward to the error page
+                        forwardToMessagePage(httpServletRequest, 
httpServletResponse, OIDC_ID_TOKEN_AUTHN_ERROR + e.getMessage());
+                        return;
+                    }
+
+                    // Get the OIDC end session endpoint
+                    URI endSessionEndpoint = 
oidcService.getEndSessionEndpoint();
+                    String postLogoutRedirectUri = generateResourceUri("..", 
"nifi", "logout-complete");
+
+                    if (endSessionEndpoint == null) {
+                        logger.debug("Unable to log out of the OpenId Connect 
Provider. The end session endpoint is: null." +
+                                " Redirecting to the logout page.");
+                        
httpServletResponse.sendRedirect(postLogoutRedirectUri);
+                    } else {
+                        URI logoutUri = UriBuilder.fromUri(endSessionEndpoint)
+                                .queryParam("id_token_hint", idToken)
+                                .queryParam("post_logout_redirect_uri", 
postLogoutRedirectUri)
+                                .build();
+                        httpServletResponse.sendRedirect(logoutUri.toString());
+                    }
+                    break;
+            }
         } else {
-            URI logoutUri = UriBuilder.fromUri(endSessionEndpoint)
-                    .queryParam("post_logout_redirect_uri", 
postLogoutRedirectUri)
-                    .build();
-            httpServletResponse.sendRedirect(logoutUri.toString());
+            // remove the oidc request cookie
+            removeOidcRequestCookie(httpServletResponse);
+
+            // report the unsuccessful logout
+            final AuthenticationErrorResponse errorOidcResponse = 
(AuthenticationErrorResponse) oidcResponse;
+            forwardToMessagePage(httpServletRequest, httpServletResponse, 
"Unsuccessful logout attempt: "
+                    + errorOidcResponse.getErrorObject().getDescription());
         }
     }
 
@@ -394,7 +585,7 @@ public class AccessResource extends ApplicationResource {
     public void knoxCallback(@Context HttpServletRequest httpServletRequest, 
@Context HttpServletResponse httpServletResponse) throws Exception {
         // only consider user specific access over https
         if (!httpServletRequest.isSecure()) {
-            forwardToMessagePage(httpServletRequest, httpServletResponse, 
"User authentication/authorization is only supported when running over HTTPS.");
+            forwardToMessagePage(httpServletRequest, httpServletResponse, 
AUTHENTICATION_NOT_ENABLED_MSG);
             return;
         }
 
@@ -768,7 +959,7 @@ public class AccessResource extends ApplicationResource {
     )
     public Response logOut(@Context HttpServletRequest httpServletRequest, 
@Context HttpServletResponse httpServletResponse) {
         if (!httpServletRequest.isSecure()) {
-            throw new IllegalStateException("User authentication/authorization 
is only supported when running over HTTPS.");
+            throw new IllegalStateException(AUTHENTICATION_NOT_ENABLED_MSG);
         }
 
         String userIdentity = NiFiUserUtils.getNiFiUserIdentity();
@@ -828,6 +1019,14 @@ public class AccessResource extends ApplicationResource {
         return generateResourceUri("access", "oidc", "callback");
     }
 
+    private String getOidcLogoutCallback() {
+        return generateResourceUri("access", "oidc", "logoutCallback");
+    }
+
+    private URI getRevokeEndpoint() {
+        return oidcService.getRevocationEndpoint();
+    }
+
     private String getNiFiUri() {
         final String nifiApiUrl = generateResourceUri();
         final String baseUrl = StringUtils.substringBeforeLast(nifiApiUrl, 
"/nifi-api");
@@ -851,6 +1050,98 @@ public class AccessResource extends ApplicationResource {
         
uiContext.getRequestDispatcher("/WEB-INF/pages/message-page.jsp").forward(httpServletRequest,
 httpServletResponse);
     }
 
+    private String determineLogoutMethod(String oidcDiscoveryUrl) {
+        Matcher accessTokenMatcher = 
REVOKE_ACCESS_TOKEN_LOGOUT_FORMAT.matcher(oidcDiscoveryUrl);
+        Matcher idTokenMatcher = 
ID_TOKEN_LOGOUT_FORMAT.matcher(oidcDiscoveryUrl);
+
+        if (accessTokenMatcher.find()) {
+            return REVOKE_ACCESS_TOKEN_LOGOUT;
+        } else if (idTokenMatcher.find()) {
+            return ID_TOKEN_LOGOUT;
+        } else {
+            return STANDARD_LOGOUT;
+        }
+    }
+
+    /**
+     * Generates the request Authorization URI for the OpenID Connect 
Provider. Returns an authorization
+     * URI using the provided callback URI.
+     *
+     * @param httpServletResponse the servlet response
+     * @param callback the OIDC callback URI
+     * @return the authorization URI
+     */
+    private URI oidcRequestAuthorizationCode(@Context HttpServletResponse 
httpServletResponse, String callback) {
+
+        final String oidcRequestIdentifier = UUID.randomUUID().toString();
+
+        // generate a cookie to associate this login sequence
+        final Cookie cookie = new Cookie(OIDC_REQUEST_IDENTIFIER, 
oidcRequestIdentifier);
+        cookie.setPath("/");
+        cookie.setHttpOnly(true);
+        cookie.setMaxAge(60);
+        cookie.setSecure(true);
+        httpServletResponse.addCookie(cookie);
+
+        // get the state for this request
+        final State state = oidcService.createState(oidcRequestIdentifier);
+
+        // build the authorization uri
+        final URI authorizationUri = 
UriBuilder.fromUri(oidcService.getAuthorizationEndpoint())
+                .queryParam("client_id", oidcService.getClientId())
+                .queryParam("response_type", "code")
+                .queryParam("scope", oidcService.getScope().toString())
+                .queryParam("state", state.getValue())
+                .queryParam("redirect_uri", callback)
+                .build();
+
+        // return Authorization URI
+        return authorizationUri;
+    }
+
+    /**
+     * Sends a POST request to the revoke endpoint to log out of the ID 
Provider.
+     *
+     * @param httpServletResponse the servlet response
+     * @param accessToken the OpenID Connect Provider access token
+     * @param revokeEndpoint the name of the cookie
+     * @throws IOException exceptional case for communication error with the 
OpenId Connect Provider
+     */
+
+    private void revokeEndpointRequest(@Context HttpServletResponse 
httpServletResponse, String accessToken, URI revokeEndpoint) throws IOException 
{
+
+        RequestConfig config = RequestConfig.custom()
+                .setConnectTimeout(msTimeout)
+                .setConnectionRequestTimeout(msTimeout)
+                .setSocketTimeout(msTimeout)
+                .build();
+
+        CloseableHttpClient httpClient = HttpClientBuilder
+                .create()
+                .setDefaultRequestConfig(config)
+                .build();
+        HttpPost httpPost = new HttpPost(revokeEndpoint);
+
+        List<NameValuePair> params = new ArrayList<>();
+        // Append a query param with the access token
+        params.add(new BasicNameValuePair("token", accessToken));
+        httpPost.setEntity(new UrlEncodedFormEntity(params));
+
+        try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
+            httpClient.close();
+
+            if (response.getStatusLine().getStatusCode() == 
HTTPResponse.SC_OK) {
+                // Redirect to logout page
+                logger.debug("You are logged out of the OpenId Connect 
Provider.");
+                String postLogoutRedirectUri = generateResourceUri("..", 
"nifi", "logout-complete");
+                httpServletResponse.sendRedirect(postLogoutRedirectUri);
+            } else {
+                logger.error("There was an error logging out of the OpenId 
Connect Provider. " +
+                        "Response status: " + 
response.getStatusLine().getStatusCode());
+            }
+        }
+    }
+
     // setters
 
     public void setLoginIdentityProvider(LoginIdentityProvider 
loginIdentityProvider) {
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/OidcIdentityProvider.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/OidcIdentityProvider.java
index cecd792..8be9b6d 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/OidcIdentityProvider.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/OidcIdentityProvider.java
@@ -22,6 +22,7 @@ import com.nimbusds.oauth2.sdk.Scope;
 import com.nimbusds.oauth2.sdk.id.ClientID;
 import java.io.IOException;
 import java.net.URI;
+import org.apache.nifi.web.security.token.LoginAuthenticationToken;
 
 public interface OidcIdentityProvider {
 
@@ -61,6 +62,13 @@ public interface OidcIdentityProvider {
     URI getEndSessionEndpoint();
 
     /**
+     * Returns the URI for the revocation endpoint.
+     *
+     * @return uri for the revocation endpoint
+     */
+    URI getRevocationEndpoint();
+
+    /**
      * Returns the scopes supported by the OIDC provider.
      *
      * @return support scopes
@@ -68,12 +76,30 @@ public interface OidcIdentityProvider {
     Scope getScope();
 
     /**
-     * Exchanges the supplied authorization grant for an ID token. Extracts 
the identity from the ID
-     * token and converts it into NiFi JWT.
+     * Exchanges the supplied authorization grant for a Login ID Token. 
Extracts the identity from the ID
+     * token.
+     *
+     * @param authorizationGrant authorization grant for invoking the Token 
Endpoint
+     * @return a Login Authentication Token
+     * @throws IOException if there was an exceptional error while 
communicating with the OIDC provider
+     */
+    LoginAuthenticationToken 
exchangeAuthorizationCodeforLoginAuthenticationToken(AuthorizationGrant 
authorizationGrant) throws IOException;
+
+    /**
+     * Exchanges the supplied authorization grant for an Access Token.
+     *
+     * @param authorizationGrant authorization grant for invoking the Token 
Endpoint
+     * @return an Access Token String
+     * @throws Exception if there was an exceptional error while communicating 
with the OIDC provider
+     */
+    String exchangeAuthorizationCodeForAccessToken(AuthorizationGrant 
authorizationGrant) throws Exception;
+
+    /**
+     * Exchanges the supplied authorization grant for an ID Token.
      *
      * @param authorizationGrant authorization grant for invoking the Token 
Endpoint
-     * @return a NiFi JWT
+     * @return an ID Token String
      * @throws IOException if there was an exceptional error while 
communicating with the OIDC provider
      */
-    String exchangeAuthorizationCode(AuthorizationGrant authorizationGrant) 
throws IOException;
+    String exchangeAuthorizationCodeForIdToken(final AuthorizationGrant 
authorizationGrant) throws IOException;
 }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/OidcService.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/OidcService.java
index b749085..5474c43 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/OidcService.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/OidcService.java
@@ -29,6 +29,7 @@ import java.security.MessageDigest;
 import java.security.SecureRandom;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
+import org.apache.nifi.web.security.token.LoginAuthenticationToken;
 import org.apache.nifi.web.security.util.CacheKey;
 
 import static 
org.apache.nifi.web.security.oidc.StandardOidcIdentityProvider.OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED;
@@ -99,6 +100,15 @@ public class OidcService {
     }
 
     /**
+     * Returns the OpenId Connect revocation endpoint.
+     *
+     * @return the revocation endpoint
+     */
+    public URI getRevocationEndpoint() {
+        return identityProvider.getRevocationEndpoint();
+    }
+
+    /**
      * Returns the OpenId Connect scope.
      *
      * @return scope
@@ -186,42 +196,72 @@ public class OidcService {
     }
 
     /**
-     * Exchanges the specified authorization grant for an ID token for the 
given request identifier.
+     * Exchanges the specified authorization grant for an ID token.
      *
-     * @param oidcRequestIdentifier request identifier
      * @param authorizationGrant authorization grant
+     * @return a Login Authentication Token
      * @throws IOException exceptional case for communication error with the 
OpenId Connect provider
      */
-    public void exchangeAuthorizationCode(final String oidcRequestIdentifier, 
final AuthorizationGrant authorizationGrant) throws IOException {
+    public LoginAuthenticationToken 
exchangeAuthorizationCodeForLoginAuthenticationToken(final AuthorizationGrant 
authorizationGrant) throws IOException {
         if (!isOidcEnabled()) {
             throw new 
IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED);
         }
 
-        final CacheKey oidcRequestIdentifierKey = new 
CacheKey(oidcRequestIdentifier);
-        final String nifiJwt = retrieveNifiJwt(authorizationGrant);
+        // Retrieve Login Authentication Token
+        return 
identityProvider.exchangeAuthorizationCodeforLoginAuthenticationToken(authorizationGrant);
+    }
 
-        try {
-            // cache the jwt for later retrieval
-            synchronized (jwtLookupForCompletedRequests) {
-                final String cachedJwt = 
jwtLookupForCompletedRequests.get(oidcRequestIdentifierKey, () -> nifiJwt);
-                if (!timeConstantEqualityCheck(nifiJwt, cachedJwt)) {
-                    throw new IllegalStateException("An existing login request 
is already in progress.");
-                }
-            }
-        } catch (final ExecutionException e) {
-            throw new IllegalStateException("Unable to store the login 
authentication token.");
+    /**
+     * Exchanges the specified authorization grant for an access token.
+     *
+     * @param authorizationGrant authorization grant
+     * @return an Access Token string
+     * @throws IOException exceptional case for communication error with the 
OpenId Connect provider
+     */
+    public String exchangeAuthorizationCodeForAccessToken(final 
AuthorizationGrant authorizationGrant) throws Exception {
+        if (!isOidcEnabled()) {
+            throw new 
IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED);
         }
+
+        // Retrieve access token
+        return 
identityProvider.exchangeAuthorizationCodeForAccessToken(authorizationGrant);
     }
 
     /**
-     * Exchange the authorization code to retrieve a NiFi JWT.
+     * Exchanges the specified authorization grant for an ID Token.
      *
      * @param authorizationGrant authorization grant
-     * @return NiFi JWT
+     * @return an ID Token string
      * @throws IOException exceptional case for communication error with the 
OpenId Connect provider
      */
-    public String retrieveNifiJwt(final AuthorizationGrant authorizationGrant) 
throws IOException {
-        return identityProvider.exchangeAuthorizationCode(authorizationGrant);
+    public String exchangeAuthorizationCodeForIdToken(final AuthorizationGrant 
authorizationGrant) throws IOException {
+        if (!isOidcEnabled()) {
+            throw new 
IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED);
+        }
+
+        // Retrieve ID token
+        return 
identityProvider.exchangeAuthorizationCodeForIdToken(authorizationGrant);
+    }
+
+    /**
+     * Stores the NiFi Jwt.
+     *
+     * @param oidcRequestIdentifier request identifier
+     * @param jwt NiFi JWT
+     */
+    public void storeJwt(final String oidcRequestIdentifier, final String jwt) 
{
+        final CacheKey oidcRequestIdentifierKey = new 
CacheKey(oidcRequestIdentifier);
+        try {
+            // Cache the jwt for later retrieval
+            synchronized (jwtLookupForCompletedRequests) {
+                final String cachedJwt = 
jwtLookupForCompletedRequests.get(oidcRequestIdentifierKey, () -> jwt);
+                if (!timeConstantEqualityCheck(jwt, cachedJwt)) {
+                    throw new IllegalStateException("An existing login request 
is already in progress.");
+                }
+            }
+        } catch (final ExecutionException e) {
+            throw new IllegalStateException("Unable to store the login 
authentication token.");
+        }
     }
 
     /**
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/StandardOidcIdentityProvider.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/StandardOidcIdentityProvider.java
index f7b54f1..a26926e 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/StandardOidcIdentityProvider.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/oidc/StandardOidcIdentityProvider.java
@@ -38,6 +38,7 @@ import com.nimbusds.oauth2.sdk.auth.Secret;
 import com.nimbusds.oauth2.sdk.http.HTTPRequest;
 import com.nimbusds.oauth2.sdk.http.HTTPResponse;
 import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.token.AccessToken;
 import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
 import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
 import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser;
@@ -45,10 +46,13 @@ import 
com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
 import com.nimbusds.openid.connect.sdk.UserInfoRequest;
 import com.nimbusds.openid.connect.sdk.UserInfoResponse;
 import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
+import com.nimbusds.openid.connect.sdk.claims.AccessTokenHash;
 import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
 import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
 import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
+import com.nimbusds.openid.connect.sdk.validators.AccessTokenValidator;
 import com.nimbusds.openid.connect.sdk.validators.IDTokenValidator;
+import com.nimbusds.openid.connect.sdk.validators.InvalidHashException;
 import java.io.IOException;
 import java.net.URI;
 import java.net.URL;
@@ -159,18 +163,7 @@ public class StandardOidcIdentityProvider implements 
OidcIdentityProvider {
 
         try {
             // get the preferred json web signature algorithm
-            final String rawPreferredJwsAlgorithm = 
properties.getOidcPreferredJwsAlgorithm();
-
-            final JWSAlgorithm preferredJwsAlgorithm;
-            if (StringUtils.isBlank(rawPreferredJwsAlgorithm)) {
-                preferredJwsAlgorithm = JWSAlgorithm.RS256;
-            } else {
-                if ("none".equalsIgnoreCase(rawPreferredJwsAlgorithm)) {
-                    preferredJwsAlgorithm = null;
-                } else {
-                    preferredJwsAlgorithm = 
JWSAlgorithm.parse(rawPreferredJwsAlgorithm);
-                }
-            }
+            final JWSAlgorithm preferredJwsAlgorithm = extractJwsAlgorithm();
 
             if (preferredJwsAlgorithm == null) {
                 tokenValidator = new 
IDTokenValidator(oidcProviderMetadata.getIssuer(), clientId);
@@ -185,6 +178,23 @@ public class StandardOidcIdentityProvider implements 
OidcIdentityProvider {
         }
     }
 
+    private JWSAlgorithm extractJwsAlgorithm() {
+
+        final String rawPreferredJwsAlgorithm = 
properties.getOidcPreferredJwsAlgorithm();
+
+        final JWSAlgorithm preferredJwsAlgorithm;
+        if (StringUtils.isBlank(rawPreferredJwsAlgorithm)) {
+            preferredJwsAlgorithm = JWSAlgorithm.RS256;
+        } else {
+            if ("none".equalsIgnoreCase(rawPreferredJwsAlgorithm)) {
+                preferredJwsAlgorithm = null;
+            } else {
+                preferredJwsAlgorithm = 
JWSAlgorithm.parse(rawPreferredJwsAlgorithm);
+            }
+        }
+        return preferredJwsAlgorithm;
+    }
+
     /**
      * Loads the initial configuration values relating to the OIDC provider 
from the class {@link NiFiProperties} and populates the individual fields.
      */
@@ -262,7 +272,6 @@ public class StandardOidcIdentityProvider implements 
OidcIdentityProvider {
         if (!isOidcEnabled()) {
             throw new 
IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED);
         }
-
         return oidcProviderMetadata.getAuthorizationEndpointURI();
     }
 
@@ -275,6 +284,14 @@ public class StandardOidcIdentityProvider implements 
OidcIdentityProvider {
     }
 
     @Override
+    public URI getRevocationEndpoint() {
+        if (!isOidcEnabled()) {
+            throw new 
IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED);
+        }
+        return oidcProviderMetadata.getRevocationEndpointURI();
+    }
+
+    @Override
     public Scope getScope() {
         if (!isOidcEnabled()) {
             throw new 
IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED);
@@ -295,37 +312,101 @@ public class StandardOidcIdentityProvider implements 
OidcIdentityProvider {
         if (!isOidcEnabled()) {
             throw new 
IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED);
         }
-
         return clientId;
     }
 
     @Override
-    public String exchangeAuthorizationCode(final AuthorizationGrant 
authorizationGrant) throws IOException {
+    public LoginAuthenticationToken 
exchangeAuthorizationCodeforLoginAuthenticationToken(final AuthorizationGrant 
authorizationGrant) throws IOException {
         // Check if OIDC is enabled
         if (!isOidcEnabled()) {
             throw new 
IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED);
         }
 
-        // Build ClientAuthentication
-        final ClientAuthentication clientAuthentication = 
createClientAuthentication();
+        try {
+            // Authenticate and authorize the client request
+            final TokenResponse response = authorizeClient(authorizationGrant);
+
+            // Convert response to Login Authentication Token
+            // We only want to do this for login
+            return 
convertOIDCTokenToLoginAuthenticationToken((OIDCTokenResponse) response);
+
+        } catch (final RuntimeException | ParseException | JOSEException | 
BadJOSEException | java.text.ParseException e) {
+            throw new RuntimeException("Unable to parse the response from the 
Token request: " + e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public String exchangeAuthorizationCodeForAccessToken(final 
AuthorizationGrant authorizationGrant) throws Exception {
+        // Check if OIDC is enabled
+        if (!isOidcEnabled()) {
+            throw new 
IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED);
+        }
 
         try {
-            // Build the token request
-            final HTTPRequest tokenHttpRequest = 
createTokenHTTPRequest(authorizationGrant, clientAuthentication);
-            return authorizeClient(tokenHttpRequest);
+            // Authenticate and authorize the client request
+            final TokenResponse response = authorizeClient(authorizationGrant);
+            return getAccessTokenString((OIDCTokenResponse) response);
 
-        } catch (final ParseException | JOSEException | BadJOSEException | 
java.text.ParseException e) {
-            throw new RuntimeException("Unable to parse the response from the 
Token request: " + e.getMessage());
+        } catch (final RuntimeException | ParseException | IOException | 
java.text.ParseException | InvalidHashException e) {
+            throw new RuntimeException("Unable to parse the response from the 
Token request: " + e.getMessage(), e);
         }
     }
 
-    private String authorizeClient(HTTPRequest tokenHttpRequest) throws 
ParseException, IOException, BadJOSEException, JOSEException, 
java.text.ParseException {
+    @Override
+    public String exchangeAuthorizationCodeForIdToken(final AuthorizationGrant 
authorizationGrant) {
+        // Check if OIDC is enabled
+        if (!isOidcEnabled()) {
+            throw new 
IllegalStateException(OPEN_ID_CONNECT_SUPPORT_IS_NOT_CONFIGURED);
+        }
+
+        try {
+            // Authenticate and authorize the client request
+            final TokenResponse response = authorizeClient(authorizationGrant);
+            return getIdTokenString((OIDCTokenResponse) response);
+
+        } catch (final RuntimeException | JOSEException | BadJOSEException | 
ParseException | IOException e) {
+            throw new RuntimeException("Unable to parse the response from the 
Token request: " + e.getMessage(), e);
+        }
+    }
+
+    private String getAccessTokenString(final OIDCTokenResponse response) 
throws Exception {
+        final OIDCTokens oidcTokens = getOidcTokens(response);
+
+        // Validate the Access Token
+        validateAccessToken(oidcTokens);
+
+        // Return the Access Token String
+        return oidcTokens.getAccessToken().getValue();
+    }
+
+    private String getIdTokenString(OIDCTokenResponse response) throws 
BadJOSEException, JOSEException {
+        final OIDCTokens oidcTokens = getOidcTokens(response);
+
+        // Validate the Token - no nonce required for authorization code flow
+        validateIdToken(oidcTokens.getIDToken());
+
+        // Return the ID Token string
+        return oidcTokens.getIDTokenString();
+    }
+
+    private TokenResponse authorizeClient(AuthorizationGrant 
authorizationGrant) throws ParseException, IOException {
+        // Build ClientAuthentication
+        final ClientAuthentication clientAuthentication = 
createClientAuthentication();
+
+        // Build the token request
+        final HTTPRequest tokenHttpRequest = 
createTokenHTTPRequest(authorizationGrant, clientAuthentication);
+
+        // Send the request and parse for success
+        return authorizeClientRequest(tokenHttpRequest);
+    }
+
+    private TokenResponse authorizeClientRequest(HTTPRequest tokenHttpRequest) 
throws ParseException, IOException {
         // Get the token response
         final TokenResponse response = 
OIDCTokenResponseParser.parse(tokenHttpRequest.send());
 
         // Handle success
         if (response.indicatesSuccess()) {
-            return convertOIDCTokenToNiFiToken((OIDCTokenResponse) response);
+            return response;
         } else {
             // If the response was not successful
             final TokenErrorResponse errorResponse = (TokenErrorResponse) 
response;
@@ -334,15 +415,14 @@ public class StandardOidcIdentityProvider implements 
OidcIdentityProvider {
         }
     }
 
-    private String convertOIDCTokenToNiFiToken(OIDCTokenResponse response) 
throws BadJOSEException, JOSEException, java.text.ParseException, IOException {
-        final OIDCTokenResponse oidcTokenResponse = response;
-        final OIDCTokens oidcTokens = oidcTokenResponse.getOIDCTokens();
+    private LoginAuthenticationToken 
convertOIDCTokenToLoginAuthenticationToken(OIDCTokenResponse response) throws 
BadJOSEException, JOSEException, java.text.ParseException, IOException {
+        final OIDCTokens oidcTokens = getOidcTokens(response);
         final JWT oidcJwt = oidcTokens.getIDToken();
 
-        // validate the token - no nonce required for authorization code flow
-        final IDTokenClaimsSet claimsSet = tokenValidator.validate(oidcJwt, 
null);
+        // Validate the token
+        final IDTokenClaimsSet claimsSet = validateIdToken(oidcJwt);
 
-        // attempt to extract the configured claim to access the user's 
identity; default is 'email'
+        // Attempt to extract the configured claim to access the user's 
identity; default is 'email'
         String identityClaim = properties.getOidcClaimIdentifyingUser();
         String identity = claimsSet.getStringClaim(identityClaim);
 
@@ -364,26 +444,30 @@ public class StandardOidcIdentityProvider implements 
OidcIdentityProvider {
             }
         }
 
-        // extract expiration details from the claims set
+        // Extract expiration details from the claims set
         final Calendar now = Calendar.getInstance();
         final Date expiration = claimsSet.getExpirationTime();
         final long expiresIn = expiration.getTime() - now.getTimeInMillis();
 
-        // convert into a nifi jwt for retrieval later
-        final LoginAuthenticationToken loginToken = new 
LoginAuthenticationToken(identity, identity, expiresIn,
-                claimsSet.getIssuer().getValue());
-        return jwtService.generateSignedToken(loginToken);
+        // Convert into a NiFi JWT for retrieval later
+        final LoginAuthenticationToken loginToken = new 
LoginAuthenticationToken(
+                identity, identity, expiresIn, 
claimsSet.getIssuer().getValue());
+        return loginToken;
+    }
+
+    private OIDCTokens getOidcTokens(OIDCTokenResponse response) {
+        return response.getOIDCTokens();
     }
 
     private String retrieveIdentityFromUserInfoEndpoint(OIDCTokens oidcTokens) 
throws IOException {
-        // explicitly try to get the identity from the UserInfo endpoint with 
the configured claim
-        // extract the bearer access token
+        // Explicitly try to get the identity from the UserInfo endpoint with 
the configured claim
+        // Extract the bearer access token
         final BearerAccessToken bearerAccessToken = 
oidcTokens.getBearerAccessToken();
         if (bearerAccessToken == null) {
             throw new IllegalStateException("No access token found in the ID 
tokens");
         }
 
-        // invoke the UserInfo endpoint
+        // Invoke the UserInfo endpoint
         HTTPRequest userInfoRequest = createUserInfoRequest(bearerAccessToken);
         return lookupIdentityInUserInfo(userInfoRequest);
     }
@@ -428,6 +512,38 @@ public class StandardOidcIdentityProvider implements 
OidcIdentityProvider {
         return presentClaims;
     }
 
+    private void validateAccessToken(OIDCTokens oidcTokens) throws Exception {
+        // Get the Access Token to validate
+        final AccessToken accessToken = oidcTokens.getAccessToken();
+
+        // Get the preferredJwsAlgorithm for validation
+        final JWSAlgorithm jwsAlgorithm = extractJwsAlgorithm();
+
+        // Get the accessTokenHash for validation
+        final String atHashString = oidcTokens
+                .getIDToken()
+                .getJWTClaimsSet()
+                .getStringClaim("at_hash");
+
+        // Compute the Access Token hash
+        final AccessTokenHash atHash = new AccessTokenHash(atHashString);
+
+        try {
+            // Validate the Token
+            AccessTokenValidator.validate(accessToken, jwsAlgorithm, atHash);
+        } catch (InvalidHashException e) {
+            throw new Exception("Unable to validate the Access Token: " + 
e.getMessage());
+        }
+    }
+
+    private IDTokenClaimsSet validateIdToken(JWT oidcJwt) throws 
BadJOSEException, JOSEException {
+        try {
+            return tokenValidator.validate(oidcJwt, null);
+        } catch (BadJOSEException e) {
+            throw new BadJOSEException("Unable to validate the ID Token: " + 
e.getMessage());
+        }
+    }
+
     private String lookupIdentityInUserInfo(final HTTPRequest 
userInfoHttpRequest) throws IOException {
         try {
             // send the user request
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/oidc/OidcServiceGroovyTest.groovy
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/oidc/OidcServiceGroovyTest.groovy
new file mode 100644
index 0000000..5480ad4
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/oidc/OidcServiceGroovyTest.groovy
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.security.oidc
+
+
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod
+import com.nimbusds.oauth2.sdk.id.Issuer
+import com.nimbusds.openid.connect.sdk.SubjectType
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata
+import io.jsonwebtoken.Jwts
+import io.jsonwebtoken.SignatureAlgorithm
+import org.apache.nifi.admin.service.KeyService
+import org.apache.nifi.key.Key
+import org.apache.nifi.util.NiFiProperties
+import org.apache.nifi.web.security.jwt.JwtService
+import org.apache.nifi.web.security.token.LoginAuthenticationToken
+import org.junit.After
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+import java.util.concurrent.TimeUnit
+
+@RunWith(JUnit4.class)
+class OidcServiceGroovyTest extends GroovyTestCase {
+    private static final Logger logger = 
LoggerFactory.getLogger(OidcServiceGroovyTest.class)
+
+    private static final Key SIGNING_KEY = new Key(id: 1, identity: 
"signingKey", key: "mock-signing-key-value")
+    private static final Map<String, Object> DEFAULT_NIFI_PROPERTIES = [
+            isOidcEnabled                 : true,
+            getOidcDiscoveryUrl           : "https://localhost/oidc";,
+            isLoginIdentityProviderEnabled: false,
+            isKnoxSsoEnabled              : false,
+            getOidcConnectTimeout         : "1000",
+            getOidcReadTimeout            : "1000",
+            getOidcClientId               : "expected_client_id",
+            getOidcClientSecret           : "expected_client_secret",
+            getOidcClaimIdentifyingUser   : "username",
+            getOidcPreferredJwsAlgorithm  : ""
+    ]
+
+    // Mock collaborators
+    private static NiFiProperties mockNiFiProperties
+    private static JwtService mockJwtService = [:] as JwtService
+    private static StandardOidcIdentityProvider soip
+
+    private static final String MOCK_REQUEST_IDENTIFIER = 
"mock-request-identifier"
+    private static final String MOCK_JWT = 
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +
+            
".eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik5pRmkgT0lEQyBVbml0IFRlc3Rlci" +
+            
"IsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE2MzM5MDIyLCJpc3MiOiJuaWZpX3Vua" +
+            
"XRfdGVzdF9hdXRob3JpdHkiLCJhdWQiOiJhbGwiLCJ1c2VybmFtZSI6Im9pZGNfdGVzd" +
+            "CIsImVtYWlsIjoib2lkY190ZXN0QG5pZmkuYXBhY2hlLm9yZyJ9" +
+            ".b4NIl0RONKdVLOH0D1eObdwAEX8qX-ExqB8KuKSZFLw"
+
+    @BeforeClass
+    static void setUpOnce() throws Exception {
+        logger.metaClass.methodMissing = { String name, args ->
+            logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
+        }
+    }
+
+    @Before
+    void setUp() throws Exception {
+        mockNiFiProperties = buildNiFiProperties()
+        soip = new StandardOidcIdentityProvider(mockJwtService, 
mockNiFiProperties)
+    }
+
+    @After
+    void teardown() throws Exception {
+    }
+
+    private static NiFiProperties buildNiFiProperties(Map<String, Object> 
props = [:]) {
+        def combinedProps = DEFAULT_NIFI_PROPERTIES + props
+        def mockNFP = combinedProps.collectEntries { String k, def v ->
+            [k, { -> return v }]
+        }
+        mockNFP as NiFiProperties
+    }
+
+    private static JwtService buildJwtService() {
+        def mockJS = new JwtService([:] as KeyService) {
+            @Override
+            String generateSignedToken(LoginAuthenticationToken lat) {
+                signNiFiToken(lat)
+            }
+        }
+        mockJS
+    }
+
+    private static String signNiFiToken(LoginAuthenticationToken lat) {
+        String identity = "mockUser"
+        String USERNAME_CLAIM = "username"
+        String KEY_ID_CLAIM = "keyId"
+        Calendar expiration = Calendar.getInstance()
+        expiration.setTimeInMillis(System.currentTimeMillis() + 10_000)
+        String username = lat.getName()
+
+        return Jwts.builder().setSubject(identity)
+                .setIssuer(lat.getIssuer())
+                .setAudience(lat.getIssuer())
+                .claim(USERNAME_CLAIM, username)
+                .claim(KEY_ID_CLAIM, SIGNING_KEY.getId())
+                .setExpiration(expiration.getTime())
+                .setIssuedAt(Calendar.getInstance().getTime())
+                .signWith(SignatureAlgorithm.HS256, 
SIGNING_KEY.key.getBytes("UTF-8")).compact()
+    }
+
+    @Test
+    void testShouldStoreJwt() {
+        // Arrange
+        StandardOidcIdentityProvider soip = 
buildIdentityProviderWithMockInitializedProvider([:])
+
+        OidcService service = new OidcService(soip)
+
+        // Expected JWT
+        logger.info("EXPECTED_JWT: ${MOCK_JWT}")
+
+        // Act
+        service.storeJwt(MOCK_REQUEST_IDENTIFIER, MOCK_JWT)
+
+        // Assert
+        final String cachedJwt = service.getJwt(MOCK_REQUEST_IDENTIFIER)
+        logger.info("Cached JWT: ${cachedJwt}")
+
+        assert cachedJwt == MOCK_JWT
+    }
+
+    @Test
+    void testShouldGetJwt() {
+        // Arrange
+        StandardOidcIdentityProvider soip = 
buildIdentityProviderWithMockInitializedProvider([:])
+
+        OidcService service = new OidcService(soip)
+
+        // Expected JWT
+        logger.info("EXPECTED_JWT: ${MOCK_JWT}")
+
+        // store the jwt
+        service.storeJwt(MOCK_REQUEST_IDENTIFIER, MOCK_JWT)
+
+        // Act
+        final String retrievedJwt = service.getJwt(MOCK_REQUEST_IDENTIFIER)
+        logger.info("Retrieved JWT: ${retrievedJwt}")
+
+        // Assert
+        assert retrievedJwt == MOCK_JWT
+    }
+
+    @Test
+    void testGetJwtShouldReturnNullWithExpiredDuration() {
+        // Arrange
+        StandardOidcIdentityProvider soip = 
buildIdentityProviderWithMockInitializedProvider([:])
+
+        final int DURATION = 500
+        final TimeUnit EXPIRATION_UNITS = TimeUnit.MILLISECONDS
+        OidcService service = new OidcService(soip, DURATION, EXPIRATION_UNITS)
+
+        // Expected JWT
+        logger.info("EXPECTED_JWT: ${MOCK_JWT}")
+
+        // Store the jwt
+        service.storeJwt(MOCK_REQUEST_IDENTIFIER, MOCK_JWT)
+
+        // Put thread to sleep
+        long millis = 1000
+        Thread.sleep(millis)
+        logger.info("Thread will sleep for: ${millis} ms")
+
+        // Act
+        final String retrievedJwt = service.getJwt(MOCK_REQUEST_IDENTIFIER)
+        logger.info("Retrieved JWT: ${retrievedJwt}")
+
+        // Assert
+        assert retrievedJwt == null
+    }
+
+    private static StandardOidcIdentityProvider 
buildIdentityProviderWithMockInitializedProvider(Map<String, String> 
additionalProperties = [:]) {
+        JwtService mockJS = buildJwtService()
+        NiFiProperties mockNFP = buildNiFiProperties(additionalProperties)
+
+        // Mock OIDC provider metadata
+        Issuer mockIssuer = new Issuer("mockIssuer")
+        URI mockURI = new URI("https://localhost/oidc";)
+        OIDCProviderMetadata metadata = new OIDCProviderMetadata(mockIssuer, 
[SubjectType.PUBLIC], mockURI)
+
+        StandardOidcIdentityProvider soip = new 
StandardOidcIdentityProvider(mockJS, mockNFP) {
+            @Override
+            void initializeProvider() {
+                soip.oidcProviderMetadata = metadata
+                soip.oidcProviderMetadata["tokenEndpointAuthMethods"] = 
[ClientAuthenticationMethod.CLIENT_SECRET_BASIC]
+                soip.oidcProviderMetadata["userInfoEndpointURI"] = new 
URI("https://localhost/oidc/token";)
+            }
+        }
+        soip
+    }
+}
\ No newline at end of file
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/oidc/StandardOidcIdentityProviderGroovyTest.groovy
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/oidc/StandardOidcIdentityProviderGroovyTest.groovy
index 0c343ed..43afb9b 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/oidc/StandardOidcIdentityProviderGroovyTest.groovy
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/oidc/StandardOidcIdentityProviderGroovyTest.groovy
@@ -16,6 +16,7 @@
  */
 package org.apache.nifi.web.security.oidc
 
+import com.nimbusds.jose.JWSAlgorithm
 import com.nimbusds.jwt.JWT
 import com.nimbusds.jwt.JWTClaimsSet
 import com.nimbusds.jwt.PlainJWT
@@ -36,14 +37,16 @@ import com.nimbusds.oauth2.sdk.token.RefreshToken
 import com.nimbusds.openid.connect.sdk.Nonce
 import com.nimbusds.openid.connect.sdk.OIDCTokenResponse
 import com.nimbusds.openid.connect.sdk.SubjectType
+import com.nimbusds.openid.connect.sdk.claims.AccessTokenHash
 import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet
 import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata
 import com.nimbusds.openid.connect.sdk.token.OIDCTokens
 import com.nimbusds.openid.connect.sdk.validators.IDTokenValidator
 import groovy.json.JsonOutput
-import groovy.json.JsonSlurper
 import io.jsonwebtoken.Jwts
 import io.jsonwebtoken.SignatureAlgorithm
+import net.minidev.json.JSONObject
+import org.apache.commons.codec.binary.Base64
 import org.apache.nifi.admin.service.KeyService
 import org.apache.nifi.key.Key
 import org.apache.nifi.util.NiFiProperties
@@ -65,7 +68,8 @@ class StandardOidcIdentityProviderGroovyTest extends 
GroovyTestCase {
 
     private static final Key SIGNING_KEY = new Key(id: 1, identity: 
"signingKey", key: "mock-signing-key-value")
     private static final Map<String, Object> DEFAULT_NIFI_PROPERTIES = [
-            isOidcEnabled                 : false,
+//            isOidcEnabled                 : false,
+            isOidcEnabled                 : true,
             getOidcDiscoveryUrl           : "https://localhost/oidc";,
             isLoginIdentityProviderEnabled: false,
             isKnoxSsoEnabled              : false,
@@ -73,13 +77,19 @@ class StandardOidcIdentityProviderGroovyTest extends 
GroovyTestCase {
             getOidcReadTimeout            : 1000,
             getOidcClientId               : "expected_client_id",
             getOidcClientSecret           : "expected_client_secret",
-            getOidcClaimIdentifyingUser   : "username"
+            getOidcClaimIdentifyingUser   : "username",
+            getOidcPreferredJwsAlgorithm  : ""
     ]
 
     // Mock collaborators
     private static NiFiProperties mockNiFiProperties
     private static JwtService mockJwtService = [:] as JwtService
 
+    private static final String MOCK_JWT = 
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZ" +
+            
"SI6Ik5pRmkgT0lEQyBVbml0IFRlc3RlciIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE2MzM5MDIyLCJpc3MiOiJuaWZp"
 +
+            
"X3VuaXRfdGVzdF9hdXRob3JpdHkiLCJhdWQiOiJhbGwiLCJ1c2VybmFtZSI6Im9pZGNfdGVzdCIsImVtYWlsIjoib2lkY19"
 +
+            
"0ZXN0QG5pZmkuYXBhY2hlLm9yZyJ9.b4NIl0RONKdVLOH0D1eObdwAEX8qX-ExqB8KuKSZFLw"
+
     @BeforeClass
     static void setUpOnce() throws Exception {
         logger.metaClass.methodMissing = { String name, args ->
@@ -110,7 +120,6 @@ class StandardOidcIdentityProviderGroovyTest extends 
GroovyTestCase {
             String generateSignedToken(LoginAuthenticationToken lat) {
                 signNiFiToken(lat)
             }
-
         }
         mockJS
     }
@@ -137,9 +146,9 @@ class StandardOidcIdentityProviderGroovyTest extends 
GroovyTestCase {
     void testShouldGetAvailableClaims() {
         // Arrange
         final Map<String, String> EXPECTED_CLAIMS = [
-                "iss"           : "https://accounts.google.com";,
-                "azp"           : 
"1013352044499-05pb1ssdfuihsdfsdsdfdi8r2vike88m.apps.googleusercontent.com",
-                "aud"           : 
"1013352044499-05pb1ssdfuihsdfsdsdfdi8r2vike88m.apps.googleusercontent.com",
+                "iss"           : "https://accounts.issuer.com";,
+                "azp"           : 
"1013352044499-05pb1ssdfuihsdfsdsdfdi8r2vike88m.apps.usercontent.com",
+                "aud"           : 
"1013352044499-05pb1ssdfuihsdfsdsdfdi8r2vike88m.apps.usercontent.com",
                 "sub"           : "10703475345439756345540",
                 "email"         : "[email protected]",
                 "email_verified": "true",
@@ -346,11 +355,12 @@ class StandardOidcIdentityProviderGroovyTest extends 
GroovyTestCase {
         logger.expected(msg)
 
         // Assert
-        assert msg =~ "An error occurred while invoking the UserInfo endpoint: 
The provided username and password were not correct"
+        assert msg =~ "An error occurred while invoking the UserInfo endpoint: 
The provided username and password " +
+                "were not correct"
     }
 
     @Test
-    void testShouldConvertOIDCTokenToNiFiToken() {
+    void testShouldConvertOIDCTokenToLoginAuthenticationToken() {
         // Arrange
         StandardOidcIdentityProvider soip = 
buildIdentityProviderWithMockTokenValidator(["getOidcClaimIdentifyingUser": 
"email"])
 
@@ -358,35 +368,24 @@ class StandardOidcIdentityProviderGroovyTest extends 
GroovyTestCase {
         logger.info("OIDC Token Response: ${mockResponse.dump()}")
 
         // Act
-        String nifiToken = soip.convertOIDCTokenToNiFiToken(mockResponse)
-        logger.info("NiFi token: ${nifiToken}")
+        final String loginToken = 
soip.convertOIDCTokenToLoginAuthenticationToken(mockResponse)
+        logger.info("Login Authentication token: ${loginToken}")
 
         // Assert
+        // Split ID Token into components
+        def (String contents, String expiration) = 
loginToken.tokenize("\\[\\]")
+        logger.info("Token contents: ${contents} | Expiration: ${expiration}")
 
-        // Split JWT into components and decode Base64 to JSON
-        def (String headerB64, String payloadB64, String signatureB64) = 
nifiToken.tokenize("\\.")
-        logger.info("Header: ${headerB64} | Payload: ${payloadB64} | 
Signature: ${signatureB64}")
-        String headerJson = new String(Base64.decoder.decode(headerB64), 
"UTF-8")
-        String payloadJson = new String(Base64.decoder.decode(payloadB64), 
"UTF-8")
-        // String signatureJson = new 
String(Base64.decoder.decode(signatureB64), "UTF-8")
-
-        // Parse JSON into objects
-        def slurper = new JsonSlurper()
-        def header = slurper.parseText(headerJson)
-        logger.info("Header: ${header}")
+        assert contents =~ "LoginAuthenticationToken for 
person@nifi\\.apache\\.org issued by https://accounts\\.issuer\\.com expiring 
at"
 
-        assert header.alg == "HS256"
-
-        def payload = slurper.parseText(payloadJson)
-        logger.info("Payload: ${payload}")
-
-        assert payload.username == "[email protected]"
-        assert payload.keyId == 1
-        assert payload.exp <= System.currentTimeMillis() + 10_000
+        // Assert expiration
+        final String[] expList = expiration.split("\\s")
+        final Long expLong = Long.parseLong(expList[0])
+        assert expLong <= System.currentTimeMillis() + 10_000
     }
 
     @Test
-    void testConvertOIDCTokenToNiFiTokenShouldHandleBlankIdentity() {
+    void 
testConvertOIDCTokenToLoginAuthenticationTokenShouldHandleBlankIdentity() {
         // Arrange
         StandardOidcIdentityProvider soip = 
buildIdentityProviderWithMockTokenValidator(["getOidcClaimIdentifyingUser": 
"non-existent-claim"])
 
@@ -394,34 +393,26 @@ class StandardOidcIdentityProviderGroovyTest extends 
GroovyTestCase {
         logger.info("OIDC Token Response: ${mockResponse.dump()}")
 
         // Act
-        String nifiToken = soip.convertOIDCTokenToNiFiToken(mockResponse)
-        logger.info("NiFi token: ${nifiToken}")
+        String loginToken = 
soip.convertOIDCTokenToLoginAuthenticationToken(mockResponse)
+        logger.info("Login Authentication token: ${loginToken}")
 
         // Assert
-        // Split JWT into components and decode Base64 to JSON
-        def (String headerB64, String payloadB64, String signatureB64) = 
nifiToken.tokenize("\\.")
-        logger.info("Header: ${headerB64} | Payload: ${payloadB64} | 
Signature: ${signatureB64}")
-        String headerJson = new String(Base64.decoder.decode(headerB64), 
"UTF-8")
-        String payloadJson = new String(Base64.decoder.decode(payloadB64), 
"UTF-8")
-        // String signatureJson = new 
String(Base64.decoder.decode(signatureB64), "UTF-8")
-
-        // Parse JSON into objects
-        def slurper = new JsonSlurper()
-        def header = slurper.parseText(headerJson)
-        logger.info("Header: ${header}")
-
-        assert header.alg == "HS256"
-
-        def payload = slurper.parseText(payloadJson)
-        logger.info("Payload: ${payload}")
-
-        assert payload.username == "[email protected]"
-        assert payload.keyId == 1
-        assert payload.exp <= System.currentTimeMillis() + 10_000
+        // Split ID Token into components
+        def (String contents, String expiration) = 
loginToken.tokenize("\\[\\]")
+        logger.info("Token contents: ${contents} | Expiration: ${expiration}")
+
+        assert contents =~ "LoginAuthenticationToken for 
person@nifi\\.apache\\.org issued by https://accounts\\.issuer\\.com expiring 
at"
+
+        // Get the expiration
+        final ArrayList<String> expires = expiration.split("[\\D*]")
+        final long exp = Long.parseLong(expires[0])
+        logger.info("exp: ${exp} ms")
+
+        assert exp <= System.currentTimeMillis() + 10_000
     }
 
     @Test
-    void 
testConvertOIDCTokenToNiFiTokenShouldHandleBlankIdentityAndNoEmailClaim() {
+    void 
testConvertOIDCTokenToLoginAuthNTokenShouldHandleBlankIdentityAndNoEmailClaim() 
{
         // Arrange
         StandardOidcIdentityProvider soip = 
buildIdentityProviderWithMockTokenValidator(["getOidcClaimIdentifyingUser": 
"non-existent-claim"])
 
@@ -429,57 +420,270 @@ class StandardOidcIdentityProviderGroovyTest extends 
GroovyTestCase {
         logger.info("OIDC Token Response: ${mockResponse.dump()}")
 
         // Act
-        def msg = shouldFail {
-            String nifiToken = soip.convertOIDCTokenToNiFiToken(mockResponse)
-            logger.info("NiFi token: ${nifiToken}")
+        def msg = shouldFail(ConnectException) {
+            String loginAuthenticationToken = 
soip.convertOIDCTokenToLoginAuthenticationToken(mockResponse)
+            logger.info("Login authentication token: 
${loginAuthenticationToken}")
         }
+        logger.expected(msg)
 
         // Assert
         assert msg =~ "Connection refused|Remote host terminated the handshake"
     }
 
     @Test
-    void testShouldAuthorizeClient() {
+    void testShouldAuthorizeClientRequest() {
         // Arrange
         // Build ID Provider with mock token endpoint URI to make a connection
         StandardOidcIdentityProvider soip = 
buildIdentityProviderWithMockTokenValidator([:])
 
-        // Mock the JWT
-        def jwt = 
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik5pRmkgT0lEQyBVbml0IFRlc3RlciIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE2MzM5MDIyLCJpc3MiOiJuaWZpX3VuaXRfdGVzdF9hdXRob3JpdHkiLCJhdWQiOiJhbGwiLCJ1c2VybmFtZSI6Im9pZGNfdGVzdCIsImVtYWlsIjoib2lkY190ZXN0QG5pZmkuYXBhY2hlLm9yZyJ9.b4NIl0RONKdVLOH0D1eObdwAEX8qX-ExqB8KuKSZFLw"
-
-        def responseBody = [id_token: jwt, access_token: "some.access.token", 
refresh_token: "some.refresh.token", token_type: "bearer"]
+        def responseBody = [id_token: MOCK_JWT, access_token: 
"some.access.token", refresh_token: "some.refresh.token", token_type: "bearer"]
         HTTPRequest mockTokenRequest = mockHttpRequest(responseBody, 200, 
"HTTP OK")
 
         // Act
-        def nifiToken = soip.authorizeClient(mockTokenRequest)
-        logger.info("NiFi Token: ${nifiToken.dump()}")
+        def tokenResponse = soip.authorizeClientRequest(mockTokenRequest)
+        logger.info("Token Response: ${tokenResponse.dump()}")
 
         // Assert
-        assert nifiToken
+        assert tokenResponse
     }
 
     @Test
-    void testAuthorizeClientShouldHandleError() {
+    void testAuthorizeClientRequestShouldHandleError() {
         // Arrange
         // Build ID Provider with mock token endpoint URI to make a connection
         StandardOidcIdentityProvider soip = 
buildIdentityProviderWithMockTokenValidator([:])
 
-        // Mock the JWT
-        def jwt = 
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik5pRmkgT0lEQyBVbml0IFRlc3RlciIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE2MzM5MDIyLCJpc3MiOiJuaWZpX3VuaXRfdGVzdF9hdXRob3JpdHkiLCJhdWQiOiJhbGwiLCJ1c2VybmFtZSI6Im9pZGNfdGVzdCIsImVtYWlsIjoib2lkY190ZXN0QG5pZmkuYXBhY2hlLm9yZyJ9.b4NIl0RONKdVLOH0D1eObdwAEX8qX-ExqB8KuKSZFLw"
-
-        def responseBody = [id_token: jwt, access_token: "some.access.token", 
refresh_token: "some.refresh.token", token_type: "bearer"]
+        def responseBody = [id_token: MOCK_JWT, access_token: 
"some.access.token", refresh_token: "some.refresh.token", token_type: "bearer"]
         HTTPRequest mockTokenRequest = mockHttpRequest(responseBody, 500, 
"HTTP SERVER ERROR")
 
         // Act
         def msg = shouldFail(RuntimeException) {
-            def nifiToken = soip.authorizeClient(mockTokenRequest)
+            def nifiToken = soip.authorizeClientRequest(mockTokenRequest)
             logger.info("NiFi token: ${nifiToken}")
         }
+        logger.expected(msg)
 
         // Assert
         assert msg =~ "An error occurred while invoking the Token endpoint: 
null"
     }
 
+    @Test
+    void testShouldGetAccessTokenString() {
+        // Arrange
+        StandardOidcIdentityProvider soip = 
buildIdentityProviderWithMockTokenValidator()
+
+        // Mock access tokens
+        AccessToken mockAccessToken = new BearerAccessToken()
+        RefreshToken mockRefreshToken = new RefreshToken()
+
+        // Compute the access token hash
+        final JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256
+        AccessTokenHash EXPECTED_HASH = 
AccessTokenHash.compute(mockAccessToken, jwsAlgorithm)
+        logger.info("Expected at_hash: ${EXPECTED_HASH}")
+
+        // Create mock claims with at_hash
+        Map<String, Object> mockClaims = (["at_hash": 
EXPECTED_HASH.toString()])
+
+        // Create Claims Set
+        JWTClaimsSet mockJWTClaimsSet = new JWTClaimsSet(mockClaims)
+
+        // Create JWT
+        JWT mockJwt = new PlainJWT(mockJWTClaimsSet)
+
+        // Create OIDC Tokens
+        OIDCTokens mockOidcTokens = new OIDCTokens(mockJwt, mockAccessToken, 
mockRefreshToken)
+
+        // Create OIDC Token Response
+        OIDCTokenResponse mockResponse = new OIDCTokenResponse(mockOidcTokens)
+
+        // Act
+        String accessTokenString = soip.getAccessTokenString(mockResponse)
+        logger.info("Access token: ${accessTokenString}")
+
+        // Assert
+        assert accessTokenString
+    }
+
+    @Test
+    void testShouldValidateAccessToken() {
+        // Arrange
+        StandardOidcIdentityProvider soip = 
buildIdentityProviderWithMockTokenValidator()
+
+        // Mock access tokens
+        AccessToken mockAccessToken = new BearerAccessToken()
+        logger.info("mock access token: ${mockAccessToken.toString()}")
+        RefreshToken mockRefreshToken = new RefreshToken()
+
+        // Compute the access token hash
+        final JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256
+        AccessTokenHash EXPECTED_HASH = 
AccessTokenHash.compute(mockAccessToken, jwsAlgorithm)
+        logger.info("Expected at_hash: ${EXPECTED_HASH}")
+
+        // Create mock claim
+        final Map<String, Object> claims = 
mockClaims(["at_hash":EXPECTED_HASH.toString()])
+
+        // Create Claims Set
+        JWTClaimsSet mockJWTClaimsSet = new JWTClaimsSet(claims)
+
+        // Create JWT
+        JWT mockJwt = new PlainJWT(mockJWTClaimsSet)
+
+        // Create OIDC Tokens
+        OIDCTokens mockOidcTokens = new OIDCTokens(mockJwt, mockAccessToken, 
mockRefreshToken)
+        logger.info("mock id tokens: 
${mockOidcTokens.getIDToken().properties}")
+
+        // Act
+        String accessTokenString = soip.validateAccessToken(mockOidcTokens)
+        logger.info("Access Token: ${accessTokenString}")
+
+        // Assert
+        assert accessTokenString == null
+    }
+
+    @Test
+    void testValidateAccessTokenShouldHandleMismatchedATHash() {
+        // Arrange
+        StandardOidcIdentityProvider soip = 
buildIdentityProviderWithMockTokenValidator()
+
+        // Mock access tokens
+        AccessToken mockAccessToken = new BearerAccessToken()
+        RefreshToken mockRefreshToken = new RefreshToken()
+
+        // Compute the access token hash
+        final JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256
+        AccessTokenHash expectedHash = 
AccessTokenHash.compute(mockAccessToken, jwsAlgorithm)
+        logger.info("Expected at_hash: ${expectedHash}")
+
+        // Create mock claim with incorrect 'at_hash'
+        final Map<String, Object> claims = mockClaims()
+
+        // Create Claims Set
+        JWTClaimsSet mockJWTClaimsSet = new JWTClaimsSet(claims)
+
+        // Create JWT
+        JWT mockJwt = new PlainJWT(mockJWTClaimsSet)
+
+        // Create OIDC Tokens
+        OIDCTokens mockOidcTokens = new OIDCTokens(mockJwt, mockAccessToken, 
mockRefreshToken)
+
+        // Act
+        def msg = shouldFail(Exception) {
+            soip.validateAccessToken(mockOidcTokens)
+        }
+        logger.expected(msg)
+
+        // Assert
+        assert msg =~ "Unable to validate the Access Token: Access token hash 
\\(at_hash\\) mismatch"
+    }
+
+    @Test
+    void testShouldGetIdTokenString() {
+        // Arrange
+        StandardOidcIdentityProvider soip = 
buildIdentityProviderWithMockTokenValidator()
+
+        // Mock access tokens
+        AccessToken mockAccessToken = new BearerAccessToken()
+        RefreshToken mockRefreshToken = new RefreshToken()
+
+        // Create mock claim
+        final Map<String, Object> claims = mockClaims()
+
+        // Create Claims Set
+        JWTClaimsSet mockJWTClaimsSet = new JWTClaimsSet(claims)
+
+        // Create JWT
+        JWT mockJwt = new PlainJWT(mockJWTClaimsSet)
+
+        // Create OIDC Tokens
+        OIDCTokens mockOidcTokens = new OIDCTokens(mockJwt, mockAccessToken, 
mockRefreshToken)
+
+        final String EXPECTED_ID_TOKEN = mockOidcTokens.getIDTokenString()
+        logger.info("EXPECTED_ID_TOKEN: ${EXPECTED_ID_TOKEN}")
+
+        // Create OIDC Token Response
+        OIDCTokenResponse mockResponse = new OIDCTokenResponse(mockOidcTokens)
+
+        // Act
+        final String idTokenString = soip.getIdTokenString(mockResponse)
+        logger.info("ID Token: ${idTokenString}")
+
+        // Assert
+        assert idTokenString
+        assert idTokenString == EXPECTED_ID_TOKEN
+
+        // Assert that 'email:[email protected]' is present
+        def (String header, String payload) = idTokenString.split("\\.")
+        final byte[] idTokenBytes = Base64.decodeBase64(payload)
+        final String payloadString = new String(idTokenBytes, "UTF-8")
+        logger.info(payloadString)
+
+        assert payloadString =~ "\"email\":\"person@nifi\\.apache\\.org\""
+    }
+
+    @Test
+    void testShouldValidateIdToken() {
+        // Arrange
+        StandardOidcIdentityProvider soip = 
buildIdentityProviderWithMockTokenValidator()
+
+        // Create mock claim
+        final Map<String, Object> claims = mockClaims()
+
+        // Create Claims Set
+        JWTClaimsSet mockJWTClaimsSet = new JWTClaimsSet(claims)
+
+        // Create JWT
+        JWT mockJwt = new PlainJWT(mockJWTClaimsSet)
+
+        // Act
+        final IDTokenClaimsSet claimsSet = soip.validateIdToken(mockJwt)
+        final String claimsSetString = claimsSet.toJSONObject().toString()
+        logger.info("ID Token Claims Set: ${claimsSetString}")
+
+        // Assert
+        assert claimsSet
+        assert claimsSetString =~ "\"email\":\"person@nifi\\.apache\\.org\""
+    }
+
+    @Test
+    void testShouldGetOidcTokens() {
+        // Arrange
+        StandardOidcIdentityProvider soip = 
buildIdentityProviderWithMockTokenValidator()
+
+        // Mock access tokens
+        AccessToken mockAccessToken = new BearerAccessToken()
+        RefreshToken mockRefreshToken = new RefreshToken()
+
+        // Create mock claim
+        final Map<String, Object> claims = mockClaims()
+
+        // Create Claims Set
+        JWTClaimsSet mockJWTClaimsSet = new JWTClaimsSet(claims)
+
+        // Create JWT
+        JWT mockJwt = new PlainJWT(mockJWTClaimsSet)
+
+        // Create OIDC Tokens
+        OIDCTokens mockOidcTokens = new OIDCTokens(mockJwt, mockAccessToken, 
mockRefreshToken)
+
+        final String EXPECTED_ID_TOKEN = mockOidcTokens.getIDTokenString()
+        logger.info("EXPECTED_ID_TOKEN: ${EXPECTED_ID_TOKEN}")
+
+        // Create OIDC Token Response
+        OIDCTokenResponse mockResponse = new OIDCTokenResponse(mockOidcTokens)
+
+        // Act
+        final OIDCTokens oidcTokens = soip.getOidcTokens(mockResponse)
+        logger.info("OIDC Tokens: ${oidcTokens.toJSONObject()}")
+
+        // Assert
+        assert oidcTokens
+
+        // Assert ID Tokens match
+        final JSONObject oidcJson = oidcTokens.toJSONObject()
+        final String idToken = oidcJson["id_token"]
+        logger.info("ID Token String: ${idToken}")
+        assert idToken == EXPECTED_ID_TOKEN
+    }
 
     private StandardOidcIdentityProvider 
buildIdentityProviderWithMockTokenValidator(Map<String, String> 
additionalProperties = [:]) {
         JwtService mockJS = buildJwtService()
@@ -515,17 +719,7 @@ class StandardOidcIdentityProviderGroovyTest extends 
GroovyTestCase {
     }
 
     private OIDCTokenResponse mockOIDCTokenResponse(Map<String, Object> 
additionalClaims = [:]) {
-        final Map<String, Object> claims = [
-                "iss"           : "https://accounts.google.com";,
-                "azp"           : 
"1013352044499-05pb1ssdfuihsdfsdsdfdi8r2vike88m.apps.googleusercontent.com",
-                "aud"           : 
"1013352044499-05pb1ssdfuihsdfsdsdfdi8r2vike88m.apps.googleusercontent.com",
-                "sub"           : "10703475345439756345540",
-                "email"         : "[email protected]",
-                "email_verified": "true",
-                "at_hash"       : "JOGISUDHFiyGHDSFwV5Fah2A",
-                "iat"           : 1590022674,
-                "exp"           : 1590026274
-        ] + additionalClaims
+        Map<String, Object> claims = mockClaims(additionalClaims)
 
         // Create Claims Set
         JWTClaimsSet mockJWTClaimsSet = new JWTClaimsSet(claims)
@@ -545,6 +739,20 @@ class StandardOidcIdentityProviderGroovyTest extends 
GroovyTestCase {
         mockResponse
     }
 
+    private static Map<String, Object> mockClaims(Map<String, Object> 
additionalClaims = [:]) {
+        final Map<String, Object> claims = [
+                "iss"           : "https://accounts.issuer.com";,
+                "azp"           : 
"1013352044499-05pb1ssdfuihsdfsdsdfdi8r2vike88m.apps.usercontent.com",
+                "aud"           : 
"1013352044499-05pb1ssdfuihsdfsdsdfdi8r2vike88m.apps.usercontent.com",
+                "sub"           : "10703475345439756345540",
+                "email"         : "[email protected]",
+                "email_verified": "true",
+                "at_hash"       : "JOGISUDHFiyGHDSFwV5Fah2A",
+                "iat"           : 1590022674,
+                "exp"           : 1590026274
+        ] + additionalClaims
+        claims
+    }
 
     /**
      * Forms an {@link HTTPRequest} object which returns a static response 
when {@code send( )} is called.
@@ -574,11 +782,4 @@ class StandardOidcIdentityProviderGroovyTest extends 
GroovyTestCase {
             }
         }
     }
-
-    class MockOIDCProviderMetadata extends OIDCProviderMetadata {
-
-        MockOIDCProviderMetadata() {
-            super([:] as Issuer, [SubjectType.PUBLIC] as List<SubjectType>, 
new URI("https://localhost";))
-        }
-    }
 }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/OidcServiceTest.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/OidcServiceTest.java
index 543b003..15aedb9 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/OidcServiceTest.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/oidc/OidcServiceTest.java
@@ -18,16 +18,15 @@ package org.apache.nifi.web.security.oidc;
 
 import com.nimbusds.oauth2.sdk.AuthorizationCode;
 import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
+import com.nimbusds.oauth2.sdk.AuthorizationGrant;
 import com.nimbusds.oauth2.sdk.id.State;
-import org.junit.Test;
-
+import java.io.IOException;
 import java.net.URI;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
+import org.junit.Test;
 
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
@@ -39,32 +38,32 @@ public class OidcServiceTest {
     public static final String TEST_STATE = "test-state";
 
     @Test(expected = IllegalStateException.class)
-    public void testOidcNotEnabledCreateState() throws Exception {
+    public void testOidcNotEnabledCreateState() {
         final OidcService service = getServiceWithNoOidcSupport();
         service.createState(TEST_REQUEST_IDENTIFIER);
     }
 
     @Test(expected = IllegalStateException.class)
-    public void testCreateStateMultipleInvocations() throws Exception {
+    public void testCreateStateMultipleInvocations() {
         final OidcService service = getServiceWithOidcSupport();
         service.createState(TEST_REQUEST_IDENTIFIER);
         service.createState(TEST_REQUEST_IDENTIFIER);
     }
 
     @Test(expected = IllegalStateException.class)
-    public void testOidcNotEnabledValidateState() throws Exception {
+    public void testOidcNotEnabledValidateState() {
         final OidcService service = getServiceWithNoOidcSupport();
         service.isStateValid(TEST_REQUEST_IDENTIFIER, new State(TEST_STATE));
     }
 
     @Test
-    public void testOidcUnknownState() throws Exception {
+    public void testOidcUnknownState() {
         final OidcService service = getServiceWithOidcSupport();
         assertFalse(service.isStateValid(TEST_REQUEST_IDENTIFIER, new 
State(TEST_STATE)));
     }
 
     @Test
-    public void testValidateState() throws Exception {
+    public void testValidateState() {
         final OidcService service = getServiceWithOidcSupport();
         final State state = service.createState(TEST_REQUEST_IDENTIFIER);
         assertTrue(service.isStateValid(TEST_REQUEST_IDENTIFIER, state));
@@ -81,39 +80,44 @@ public class OidcServiceTest {
     }
 
     @Test(expected = IllegalStateException.class)
-    public void testOidcNotEnabledExchangeCode() throws Exception {
-        final OidcService service = getServiceWithNoOidcSupport();
-        service.exchangeAuthorizationCode(TEST_REQUEST_IDENTIFIER, 
getAuthorizationCodeGrant());
+    public void testStoreJwtMultipleInvocation() {
+        final OidcService service = getServiceWithOidcSupport();
+
+        final String TEST_JWT1 = 
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik5pRmkgT0"
 +
+                
"lEQyBVbml0IFRlc3RlciIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE2MzM5MDIyLCJpc3MiOiJuaWZpX3VuaXRfdGVzdF9hd"
 +
+                "XRoyZyJ9.b4NIl0RONKdVLOH0D1eObdwAEX8qX-ExqB8KuKSZFLw";
+
+        final String TEST_JWT2 = 
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5ODc2NTQzMjEwIiwibmFtZSI6Ik5pRm"
 +
+                
"kgT0lEQyBVbml0IFRlc3RlciIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE2MzM5MDIyLCJpc3MiOiJuaWZpX3VuaXRfdGVzdF"
 +
+                
"9hdXRob3JpdHkiLCJhdWQiOiJhbGwiLCJ1c2VybmFtZSI6Im9pZGNfdGVzdCIsImVtYWlsIjoib2lkY190ZXN0QG5pZmk"
 +
+                
"uYXBhY2hlLm9yZyJ9.nlYhplDLXeGAwW62rJ_ZnEaG7nxEB4TbaJNK-_pC4WQ";
+
+        service.storeJwt(TEST_REQUEST_IDENTIFIER, TEST_JWT1);
+        service.storeJwt(TEST_REQUEST_IDENTIFIER, TEST_JWT2);
     }
 
     @Test(expected = IllegalStateException.class)
-    public void testExchangeCodeMultipleInvocation() throws Exception {
-        final OidcService service = getServiceWithOidcSupport();
-        service.exchangeAuthorizationCode(TEST_REQUEST_IDENTIFIER, 
getAuthorizationCodeGrant());
-        service.exchangeAuthorizationCode(TEST_REQUEST_IDENTIFIER, 
getAuthorizationCodeGrant());
+    public void testOidcNotEnabledExchangeCodeForLoginAuthenticationToken() 
throws Exception {
+        final OidcService service = getServiceWithNoOidcSupport();
+        
service.exchangeAuthorizationCodeForLoginAuthenticationToken(getAuthorizationGrant());
     }
 
     @Test(expected = IllegalStateException.class)
-    public void testOidcNotEnabledGetJwt() throws Exception {
+    public void testOidcNotEnabledExchangeCodeForAccessToken() throws 
Exception {
         final OidcService service = getServiceWithNoOidcSupport();
-        service.getJwt(TEST_REQUEST_IDENTIFIER);
+        
service.exchangeAuthorizationCodeForAccessToken(getAuthorizationGrant());
     }
 
-    @Test
-    public void testGetJwt() throws Exception {
-        final OidcService service = getServiceWithOidcSupport();
-        service.exchangeAuthorizationCode(TEST_REQUEST_IDENTIFIER, 
getAuthorizationCodeGrant());
-        assertNotNull(service.getJwt(TEST_REQUEST_IDENTIFIER));
+    @Test(expected = IllegalStateException.class)
+    public void testOidcNotEnabledExchangeCodeForIdToken() throws IOException {
+        final OidcService service = getServiceWithNoOidcSupport();
+        service.exchangeAuthorizationCodeForIdToken(getAuthorizationGrant());
     }
 
-    @Test
-    public void testGetJwtExpiration() throws Exception {
-        final OidcService service = 
getServiceWithOidcSupportAndCustomExpiration(1, TimeUnit.SECONDS);
-        service.exchangeAuthorizationCode(TEST_REQUEST_IDENTIFIER, 
getAuthorizationCodeGrant());
-
-        Thread.sleep(3 * 1000);
-
-        assertNull(service.getJwt(TEST_REQUEST_IDENTIFIER));
+    @Test(expected = IllegalStateException.class)
+    public void testOidcNotEnabledGetJwt() {
+        final OidcService service = getServiceWithNoOidcSupport();
+        service.getJwt(TEST_REQUEST_IDENTIFIER);
     }
 
     private OidcService getServiceWithNoOidcSupport() {
@@ -126,10 +130,9 @@ public class OidcServiceTest {
         return service;
     }
 
-    private OidcService getServiceWithOidcSupport() throws Exception {
+    private OidcService getServiceWithOidcSupport() {
         final OidcIdentityProvider provider = mock(OidcIdentityProvider.class);
         when(provider.isOidcEnabled()).thenReturn(true);
-        when(provider.exchangeAuthorizationCode(any())).then(invocation -> 
UUID.randomUUID().toString());
 
         final OidcService service = new OidcService(provider);
         assertTrue(service.isOidcEnabled());
@@ -140,7 +143,7 @@ public class OidcServiceTest {
     private OidcService getServiceWithOidcSupportAndCustomExpiration(final int 
duration, final TimeUnit units) throws Exception {
         final OidcIdentityProvider provider = mock(OidcIdentityProvider.class);
         when(provider.isOidcEnabled()).thenReturn(true);
-        when(provider.exchangeAuthorizationCode(any())).then(invocation -> 
UUID.randomUUID().toString());
+        
when(provider.exchangeAuthorizationCodeforLoginAuthenticationToken(any())).then(invocation
 -> UUID.randomUUID().toString());
 
         final OidcService service = new OidcService(provider, duration, units);
         assertTrue(service.isOidcEnabled());
@@ -148,7 +151,7 @@ public class OidcServiceTest {
         return service;
     }
 
-    private AuthorizationCodeGrant getAuthorizationCodeGrant() {
+    private AuthorizationGrant getAuthorizationGrant() {
         return new AuthorizationCodeGrant(new AuthorizationCode("code"), 
URI.create("http://localhost:8080/nifi";));
     }
 }
\ No newline at end of file

Reply via email to