-
Notifications
You must be signed in to change notification settings - Fork 292
Logout ely 2534 #2249
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 2.x
Are you sure you want to change the base?
Logout ely 2534 #2249
Conversation
c66c9ab to
b3ffe0f
Compare
1123a7f to
bbc858c
Compare
pedro-hos
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM overall! One small improvement could be adjusting the debug message to match the format of other log statements. For instance, instead of:
log.debug("evaluateRequest uri: " + request.getRequestURI().toString());You might consider using log.debugf for consistency:
log.debugf("The evaluated request URI is [%s]", request.getRequestURI().toString());This keeps the style aligned with existing logs, like:
log.debugf("Ignoring request for path [%s] from mechanism [%s]. No client configuration context found." ...);That said, this is a minor improvement — everything else looks great to me!
| private static final String POST_LOGOUT_REDIRECT_URI_PARAM = "post_logout_redirect_uri"; | ||
| private static final String ID_TOKEN_HINT_PARAM = "id_token_hint"; | ||
| private static final String LOGOUT_TOKEN_PARAM = "logout_token"; | ||
| private static final String LOGOUT_TOKEN_TYPE = "Logout"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Logout" with the first capital letter is the expected value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes "Logout" is expected it looks to be a key for claim types. similarly "Bearer" is capitalized as well.
I changed the log.debug ref to log.debugf only in the code I added. I prefer to leave pre-existing code as is.
Thanks for the code review and the suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are "logout_token" and "Logout" specific to the Keycloak OpenID provider? I'm not seeing these in the OIDC logout specs. If these are Keycloak-specific, we need to be careful to make sure we are not introducing logic that won't work for providers that don't make use of these.
That reminds me, it would also be good to do some manual tests using your built WildFly server with other OpenID providers like Auth0 or Okta to make sure things work as expected with a non-Keycloak provider.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"logout_token" is defined in the Back-Channel spec, so it is not Elytron specific.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the "Logout" value for the "typ" claim looks to be specific to Keycloak. @pedroigor Can you confirm?
In the Back Channel spec, I see the following:
"Another way to prevent cross-JWT confusion is to use explicit typing, as described in Section 3.11 of [RFC8725]. One would explicitly type a Logout Token by including a typ (type) Header Parameter with a value of logout+jwt (which is registered in Section 5.3.1. Note that the application/ portion of the application/logout+jwt media type name is omitted when used as a typ Header Parameter value, as described in the typ definition in Section 4.1.9 [JWS]. Including an explicit type in issued Logout Tokens is a best practice. Note however, that requiring explicitly typed Logout Tokens will break most existing deployments, as existing OPs and RPs are already commonly using untyped Logout Tokens. However, requiring explicit typing would be a good idea for new deployment profiles where compatibility with existing deployments is not a consideration."
https://openid.net/specs/openid-connect-backchannel-1_0.html#CrossJWT
pedro-hos
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
| }); | ||
|
|
||
| boolean tryLogout(OidcHttpFacade facade) { | ||
| log.trace("tryLogout entered"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to check, I know some of these trace and debug statements were added for debugging. Do we still want to keep all of these?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can remove them but I think they could be very helpful debugging customer issues with logout.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keeping ones that make sense is fine. Might be good to revisit the messages and make sure they have sufficient details/are consistent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| public HttpScope getScope(Scope scope) { | ||
| if (requestURI != null && "/clientApp/logout/callback".equals(requestURI.getPath())){ | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this needed here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is needed to allow assertUserNotAuthenticated() after user logout to function properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just wondering if this is the right place to add this logic since getScope doesn't take the requestURI as a param.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| @Message(id = 23070, value = "Authentication request format must be one of the following: oauth2, request, request_uri.") | ||
| RuntimeException invalidAuthenticationRequestFormat(); | ||
|
|
||
| @Message(id = 23071, value = "%s is not a valid value for %s") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once the nonce changes are merged, we'll need to merge the latest changes from 2.x into this branch and these message IDs will need to be updated here.
| protected String requestObjectSigningKeyStoreType; | ||
| protected JWKEncPublicKeyLocator encryptionPublicKeyLocator; | ||
| private boolean logoutSessionRequired = true; | ||
| private String logoutSessionRequired; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this changed to a String?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an optional setting. We needed to be able to determine if the user specifically set the value or not.
If this was a boolean we would not be able to determine if the user set the value to true, false or not set at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the analysis document, it looks like the default for this should be set to true. We should be using boolean here (we have other similar boolean attributes as well). (Note that it's also possible to use Boolean but I think that will be needed for the system property case, it shouldn't be needed here.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| String tmpLogoutPath = System.getProperty(LOGOUT_PATH); | ||
| String tmpLogoutPath = oidcJsonConfiguration.getLogoutPath(); | ||
| if (tmpLogoutPath == null) { | ||
| tmpLogoutPath = System.getProperty(LOGOUT_PATH); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The system property configuration shouldn't be part of this branch. In this 2.x branch, logout should be configured using OIDC JSON configuration only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
In bbc858c, it looks like there are changes to tool/LICENSE.txt, there shouldn't be any changes there. |
| public void testLogoutPath() { | ||
|
|
||
| try { | ||
| System.setProperty(Oidc.LOGOUT_PATH, " "); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
System property related tests should be for the maintenance branch. They shouldn't be present in this 2.x branch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| /* | ||
| * JBoss, Home of Professional Open Source. | ||
| * Copyright 2021 Red Hat, Inc., and individual contributors | ||
| * Copyright 2024 Red Hat, Inc., and individual contributors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a minor comment, we don't need to update the year on existing files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
original date returned
| @Message(id = 23070, value = "Authentication request format must be one of the following: oauth2, request, request_uri.") | ||
| RuntimeException invalidAuthenticationRequestFormat(); | ||
|
|
||
| @Message(id = 23071, value = "Invalid logout output: %s is not a valid value for %s") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that the OIDC nonce PR was merged, it's a good time to add a commit that merges the latest changes from 2.x and then update the message IDs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| this.sessionRequiredOnLogout = sessionRequiredOnLogout; | ||
| } | ||
|
|
||
| public int getLogoutSessionWaitingLimit() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just thinking it might be good to rename this to make it easier to understand what it's used for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| logoutUri = redirectUriBuilder.build().toString(); | ||
| log.trace("redirectEndSessionEndpoint path: " + redirectUriBuilder.toString()); | ||
| } catch (URISyntaxException e) { | ||
| throw new RuntimeException(e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use ElytronMessages here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| JwtClaims claims; | ||
|
|
||
| try { | ||
| claims = tokenValidator.verify(logoutToken); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned above, we need to check if this "Logout" token type is Keycloak-specific because if it is, this validation will fail for OpenID providers other an than Keycloak.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"logout_token" is defined in the Back-Channel spec, so it is not Elytron specific.
| } | ||
|
|
||
| if (!isSessionRequiredOnLogout(facade)) { | ||
| log.warn("Back-channel logout request received but can not infer sid from logout token to mark it for invalidation"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be in ElytronMessages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| public static final String PROVIDER_URL = "provider-url"; | ||
| public static final String LOGOUT_PATH = "logout-path"; | ||
| public static final String LOGOUT_CALLBACK_PATH = "logout-callback-path"; | ||
| public static final String POST_LOGOUT_URI = "post-logout-uri"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the RP initiated logout spec refers to this as post_logout_redirect_uri. Any reason to not use POST_LOGOUT_REDIRECT_URI=post-logout-redirect-uri here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be changed but it will required a revision to the proposal doc,
wildfly/elytron-oidc-client schema doc and corresponding code, and the
integration tests. How would you like to proceed with this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, it's good to use names that are consistent with the spec so it's intuitive how things defined in the spec relate to things in our configuration. I think the original name that was used in the proposal was post-logout-redirect-uri. Do you recall why it was changed to to post-logout-uri?
| private String logoutPath = "/logout"; | ||
| private String logoutCallbackPath = "/logout/callback"; | ||
|
|
||
| private int logoutSessionWaitingLimit = 100; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be good to define a constant in Oidc.java for this default value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| private String postLogoutUri; | ||
| private boolean sessionRequiredOnLogout = true; | ||
| private String logoutPath = "/logout"; | ||
| private String logoutCallbackPath = "/logout/callback"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be good to define a constant in Oidc.java for the above two default values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| return tokenUrl; | ||
| } | ||
|
|
||
| public String getLogoutUrl() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this rename needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The value being return is the path component of a URL not the URL itself.
For accuracy and clarity the name was changed. In addition this variable
is directly related to the property name logout-path as defined in the
proposal doc and the elytron-oidc-client schema.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I was asking about the getLogoutUrl method being renamed to getEndSessionEndpointUrl.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see now that getEndSessionEndpointUrl wasn't part of your commits, it was actually part of Pedro's initial changes. That's fine.
| return this.encryptionPublicKeyLocator; | ||
| } | ||
|
|
||
| public void setPostLogoutUri(String postLogoutUri) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use use setPostLogoutRedirectUri(String postLogoutRedirectUri)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Elytron's coding convention appears to be to define a get/set method name
that uses the same variable name. If we change variable postLogoutUri to
postLogoutRedirectUri as discussed in a previous comment then this method
name should change as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's right, just wanted to point these references out as a reminder to myself when reviewing. Sometimes it's easy to update a variable name but forget to update some of the methods that reference it. :)
| this.postLogoutUri = postLogoutUri; | ||
| } | ||
|
|
||
| public String getPostLogoutUri() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar comment here as the one above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change dependent on previous name decision as discussed above
| oidcClientConfiguration.setTokenSignatureAlgorithm(oidcJsonConfiguration.getTokenSignatureAlgorithm()); | ||
|
|
||
| String tmpLogoutPath = oidcJsonConfiguration.getLogoutPath(); | ||
| log.debugf("sysProp LOGOUT_PATH: " + (tmpLogoutPath == null ? "NULL" : tmpLogoutPath)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This references a system property.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The special null handling isn't needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| } | ||
|
|
||
| String tmpLogoutCallbackPath = oidcJsonConfiguration.getLogoutCallbackPath(); | ||
| log.debugf("sysProp LOGOUT_CALLBACK_PATH: " + (tmpLogoutCallbackPath == null ? "NULL" : tmpLogoutCallbackPath)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be updated, currently references sysProp.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The special null handling isn't needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
Hi, do you have any updates for this feature? Last year or so it was once planned for Wildfly 37. |
|
There is no specific version for release yet but it is in the planning queue. |
…ck-channel logout
…an active session
…ing doesn't rely on an active session and ensure the session will get invalidated appropriately upon subsequent requests
…validation check to before the authenticator#authenticate call and update the check to not remove the session from the map to ensure that the user gets logged out from any other apps too
… the sessionsMarkedForInvalidation map
eb766c0 to
2edf61b
Compare
Skyllarr
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rsearls I haven't finish the review yet, just addint some in proress comments for now
| @@ -1 +0,0 @@ | |||
| src/main/resources/META-INF/LICENSE.txt No newline at end of file | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rsearls This file should not be altered? It contains a single line originally: https://github.com/wildfly-security/wildfly-elytron/blob/2.x/tool/LICENSE.txt
|
|
||
| @Message(id = 23050, value = "Invalid bearer token claims") | ||
| OidcException invalidBearerTokenClaims(); | ||
| @Message(id = 23050, value = "Invalid token claims") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rsearls Can we add another message instead of changing the existing one? For some backwards compatibility in logging
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
switching the msg order addresses the issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rsearls Can you pls clarify how this was addressed? it is just a minor, but I think that the message with ID 23050 already existed prior to this feature, so instead of editing it, we could add another message with new unique ID
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rsearls actually looking more closely, the only place where this is used is in TokenValidator. This validates both the bearer token and ID token so it might have been renamed on purpose to address both token types. Or do we know why was this renamed? Was it renamed by somebody esle in commits?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reviewed the code history and saw this before yesterdays change.
214 @message(id = 23050, value = "Invalid bearer token claims")
215 OidcException invalidBearerTokenClaims();
214 @message(id = 23050, value = "Invalid token claims")
215 OidcException invalidTokenClaims();
The first usage, "invalidBearerTokenClaims" was replaced by "invalidTokenClaims" on 3/21/25.
This was done because the message itself was changed from a reference specifically
to "bearer token" to a more generic message; the message method naming pattern
was being followed. A method call to "invalidBearerTokenClaims" no longer exists
in the code base. Most likely the message was made more generic when an
addition of nonce handling was added. Most likely the old message was returned
when a merge of elytron 2.x was performed last week to bring this branch up to date.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rsearls Hm when I look at the https://github.com/wildfly-security/wildfly-elytron/blame/2.x/http/oidc/src/main/java/org/wildfly/security/http/oidc/ElytronMessages.java#L215C32-L215C43 , it says that the 2.x branch has the invalidBearerTokenClaims since the bearer token support was added 3 years ago. Do you maybe have a different view locally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name was generalize when BackChannelLogout logoutToken
validation was added.
I think when I did the rebase of 2.x to my branch last week is when the competing messages appeared.
| String nonceCookieDoesNotExist(); | ||
|
|
||
| @Message(id = 23074, value = "Invalid logout path: %s is not a valid value for %s") | ||
| IllegalArgumentException invalidLogoutPath(String pathValue, String pathName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just a minor, I think this message and below message can accept a single parameter, as the logout path will be an attribute and always have a value of "logout-path" ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- @message(id = 23074, value = "Invalid logout path: %s is not a valid value for %s")
- IllegalArgumentException invalidLogoutPath(String pathValue, String pathName);
For clarity for the user I think the current message should be maintained
or the message changed to "%s is not a valid value for attribute logout-path".
What is your preference?
I have removed "invalidLogoutCallbackPath" as it is not being used anywhere
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rsearls My thought was changing it to be "%s is not a valid value for attribute logout-path". Or it could also be simplified to be more generic and used in more situations:
@Message(id = 23074, value = "Invalid attribute: %s is not a valid value for %s")
IllegalArgumentException invalidAttribute(String pathValue, String pathName);
But thinking more about it, since this is a preview feature, and there is a tiny possibility of the "logout-path" changing in the future, let's keep it as it is now.
Btw I can still see the "invalidLogoutCallbackPath" in these changes, so it was not removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was waiting to hear your preference for the message in "invalidAttribute" before checking in the change where I removed the "invalidLogoutCallbackPath"
| RuntimeException unableToCreateEndSessionEndpointRequest(String url, String msg); | ||
|
|
||
| @Message(id = 23077, value = "Back-channel logout request received but can not infer sid from logout token to mark it for invalidation") | ||
| String sidCanNotBeInferFromLogoutToken(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/sidCanNotBeInferFromLogoutToken/sidCanNotBeInferredFromLogoutToken
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed typo
| if (idToken == null) { | ||
| return false; | ||
| } | ||
| return sessionsMarkedForInvalidation.remove(idToken.getNonce()) != null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rsearls We are removing the key "idToken.getNonce()" from the map. But we are putting "getSessionKey(facade, sessionId)" key there on line 209.
This doesn't have to be the same key? Shouldn't we remove the getSessionKey(facade, sessionId) value here instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is to high risk a change at this time. I do not understand the
details of how IDToken or sessionId is used in these scenarios. I think it
would be safest to create a jira for this to be addressed in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rsearls I checked and we are the ones creating the NONCE value, send it with the initial login request to OpenID provider, and they send it back so we can validate it is the same value that we have sent. We are creating it in the OidcRequestAuthenticator here, and then we compare the received nonce in TokenValidator here. It seems you have added this code in the past so maybe you know whether my understanding is correct?
If so, how we use the map with different key being put there and different key removed seems to me like a bug, because the value of nonce will be random, but the value of getSessionKey(facade, sessionId) will be something like clientId-sessionId.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change implemented
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change implemented
| /** | ||
| * A bounded map to store sessions marked for invalidation after receiving logout requests through the back-channel | ||
| */ | ||
| private Map<String, OidcClientConfiguration> sessionsMarkedForInvalidation = synchronizedMap(new LinkedHashMap<String, OidcClientConfiguration>(16, 0.75f, true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rsearls Not sure if this was discussed, and it can possibly be an enhancement later. But this invalidation map will work only on the same JVM. Was there any discussion about whether it is makes sense to make it work in distributed environments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was never any discussion about this. I recommend we create a jira to
evaluate and address this in the future.
8641c40 to
91e8612
Compare
|
In branch 2.x tool/LICENSE.txt is a link to a file. I have addressed this. |
| } | ||
|
|
||
| public String getLogoutUrl() { | ||
| public String getEndSessionEndpointUrl() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a note for other reviewers, http/oidc is not part of the public API so these renames are fine
| return new AccessToken(jwtClaims); | ||
| } catch (InvalidJwtException e) { | ||
| log.tracef("Problem parsing bearer token: " + bearerToken, e); | ||
| log.tracef("Problem parsing bearer token: " + token, e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rsearls Just wondering why the message was changed from invalidBearerTokenClaims to invalidTokenClaims and if the log here should also have "bearer" removed and be: log.tracef("Problem parsing token: " + token, e);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this relates to our other discussion above about whether we are changing this message in this PR or it was changed elsewhere already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TokenVaidator.verify was generalized when BackChannelLogout logoutToken
validation was added.
LogoutHandler.handleBackChannelLogoutRequest
155 String logoutToken = facade.getRequest().getFirstParam(LOGOUT_TOKEN_PARAM);
from the request object a logoutToken is looked for. If present
it contains jwtClaim parseable information related to logout.
169 claims = tokenValidator.verify(logoutToken);
Token validation was no longer just for Bearer tokens.
BearerTokenRequestAuthenticator.verifyToken
99 token = tokenValidator.parseAndVerifyToken(tokenString);
Method parseAndVerifyToken calls method verify
TokenValidator.parseAndVerifyToken
125 return new AccessToken(verify(token));
There is no reason "bearer" can't be remove from the log msg at
line 151.
Is that your preference?
|
@rsearls Later today I plan to try to manually test all these logout mechanisms with keycloak locally. Just wondering if this is something you tried as well already? |
| try { | ||
| URL url = new URL(tmpLogoutCallbackPath); | ||
| if (uriStr.equals(url.toString()) | ||
| || uriStr.endsWith(tmpLogoutCallbackPath)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method getRelativePath() returns path that is relative to the context path of the application. So then IMO this should work: facade.getRequest().getRelativePath().equals(tmpLogoutCallbackPath)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that works in this usage. Callback path is text that can be
appended to a URL. It is text that is registered with Keycloak as the post
logout path and keycloak sends the url back on logout.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change implemented
| if (logoutPath == null) { | ||
| return false; | ||
| } | ||
| return path.endsWith(logoutPath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO here we can also use equals instead of endsWith
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this is for the same reason as above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing this to equals caused tests to fail.
| facade.getTokenStore().logout(false); | ||
| } | ||
|
|
||
| private String getRedirectUri(OidcHttpFacade facade) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a detail, this method is unused
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed it.
|
|
||
| @Message(id = 23050, value = "Invalid bearer token claims") | ||
| OidcException invalidBearerTokenClaims(); | ||
| @Message(id = 23050, value = "Invalid token claims") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rsearls Hm when I look at the https://github.com/wildfly-security/wildfly-elytron/blame/2.x/http/oidc/src/main/java/org/wildfly/security/http/oidc/ElytronMessages.java#L215C32-L215C43 , it says that the 2.x branch has the invalidBearerTokenClaims since the bearer token support was added 3 years ago. Do you maybe have a different view locally?
| IDToken idToken = context.getIDToken(); | ||
| String issuer = request.getQueryParamValue(ISS); | ||
|
|
||
| if (idToken == null || !sessionId.equals(idToken.getNonce()) || !idToken.getIssuer().equals(issuer)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is another instance of comparing sessionId (from sid query parameter) to idToken.getNonce() . Not sure why since these are different concepts it seems to me. I wonder if Keycloak configures these values to match, but other providers might not match them. we will need to investigate this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed
| private void handleBackChannelLogoutRequest(OidcHttpFacade facade) { | ||
|
|
||
| OidcClientConfiguration clientConfiguration = facade.getOidcClientConfiguration(); | ||
| String logoutToken = facade.getRequest().getFirstParam(LOGOUT_TOKEN_PARAM); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rsearls Should we add a null check for logout token parameter here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could do a null check however an InvalidJwtException exception will be thrown by the TokenValidation code. There is logging in place for that.
Do you want a null check to be added to the code any way?
|
|
||
| try { | ||
| URIBuilder redirectUriBuilder = new URIBuilder(clientConfiguration.getEndSessionEndpointUrl()) | ||
| .addParameter(ID_TOKEN_HINT_PARAM, securityContext.getIDTokenString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe let's add a null check for securityContext.getIDTokenString() so that we do not add a parameter to the URI with null value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added the check.
|
I have not manually tested logout recently. It was do in the past. |
698cc13 to
261b914
Compare
http/oidc/src/main/java/org/wildfly/security/http/oidc/TokenValidator.java
Show resolved
Hide resolved
|
|
||
| @Override | ||
| public void evaluateRequest(HttpServerRequest request) throws HttpAuthenticationException { | ||
| log.debugf("evaluateRequest uri: " + request.getRequestURI().toString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| log.debugf("evaluateRequest uri: " + request.getRequestURI().toString()); | |
| log.debugf("Evaluating request URI: [%s]", request.getRequestURI()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are more of those. I'm not sure which are meant to stay.
There are also several debugf where debug would be sufficient instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All other such log msg are pre-existing. I don't think they should be changed.
| OidcClientConfiguration clientConfiguration = facade.getOidcClientConfiguration(); | ||
| String logoutToken = facade.getRequest().getFirstParam(LOGOUT_TOKEN_PARAM); | ||
| TokenValidator.Builder tokenBuilder = TokenValidator.builder(clientConfiguration) | ||
| .setSkipExpirationValidator(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do I understand it correctly that we skip the Logout Token expiration validation? Is there another way to prevent captured Logout Tokens from being replayable we rely on?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know. Perhaps @pedroigor or @Skyllarr could comment on this.
| String sessionId = claims.getClaimValueAsString(SID); | ||
|
|
||
| if (sessionId == null) { | ||
| facade.getResponse().setStatus(HttpStatus.SC_BAD_REQUEST); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean that we do not support logout for all sessions (at once) at the RP for the End-User identified by the iss and sub Claims? If yes, it could be useful to add the fact to the analysis document.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps @pedroigor would like to comment on this.
|
|
||
| OidcClientConfiguration clientConfiguration = facade.getOidcClientConfiguration(); | ||
| String logoutToken = facade.getRequest().getFirstParam(LOGOUT_TOKEN_PARAM); | ||
| TokenValidator.Builder tokenBuilder = TokenValidator.builder(clientConfiguration) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cannot find events Claim content validation, and not-presence of nonce Claim validation required by the specification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are various types of claims validators in TokenValidator. There are several inner classes there are run for validation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. Nonetheless it seems the validation required by the specification is missing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved
https://issues.redhat.com/browse/ELY-2534
supersede #2245