Skip to content
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

Add support for OIDC BackChannel Logout #24611

Merged
merged 1 commit into from
Apr 13, 2022

Conversation

sberyozkin
Copy link
Member

@sberyozkin sberyozkin commented Mar 29, 2022

Fixes #23477.

This Draft PR introduces a support for OIDC Back-Channel Logout. The technical difficulty in supporting it in Quarkus OIDC is to do with it being stateless, with the session cookie keeping IdToken by default, therefore a map keeping the verified Logout tokens is used to check if the returning session IdToken matches a given logout token and the session has to be cleared.

Here is a summary of the changes:

  • BackChannelLogoutHandler adds Vert.x routes for the default and static tenants if they have the back-channel path configured
  • BackChannelLogoutHandler also handles the back-channel logout requests, if it is a valid form url encoded POST request, by verifying that the logout token is generally valid (signature, optional iss, audience, has either sub or sid, has the events claim) and saves it in a concurrent map
  • CodeAuhenticationMechanism, when reauthenticating IdToken checks if a matching verified Logout Token is available and if yes then compares the some of the IdToken and LogoutToken claims (iss, sid if set, etc) and if the match is confirmed then removes the session cookie
  • Token age property is added to support only the logout tokens without the exp claim (CC @pedroigor). ID and access JWT tokens still must provide a valid exp claim.
  • Wiremock based test is added

Current restrictions:

  • It is not supported for Dynamic tenants (created with TenantConfigResolver) as it will involve the dynamic creation of Vert.x routes, and a need to restrict a total number of tenants - it can be done in the next phase
  • Logout token can only be verified with the keys fetched from OpenId Connect provider - the remote introspection (which would lead to a sequence like Keycloak -> Quarkus BackChannel Logout Request (and now Quarkus callback to Keycloak to verify logout token while holding the Keycloak to Quarkus BackChannel Logout Request) is not supported right now for the logout tokens

@orivat
Copy link

orivat commented Mar 30, 2022

@sberyozkin

#24377 is still failing

I have tested issue #23477 with the following commands:

$ git clone https://github.com/quarkusio/quarkus.git
$ gh pr checkout 24611

$ cd quarkus
export MAVEN_OPTS="-Xmx4g"
./mvnw -Dquickly

In the existing getting-started project, I have update the pom.xml to point to

io.quarkus quarkus-integration-tests-parent 999-SNAPSHOT quarkus-integration-test-resteasy-jackson Quarkus - Integration Tests - RESTEasy Jackson io.quarkus quarkus-resteasy-jackson io.quarkus quarkus-smallrye-openapi .... ...

Testing

I have executed the same test as in the bug description

step 1:

In a browser, enter http://localhost:8080/hello
---> Redirected to RH-SSO

step 2:
authenticates using user1
---> Display hello Resteasy

step 3:
Logout / terminates user session within keycloak

step 4:
In the browser URL localhost:8080/hello is still valid (which is incorrect).

Correct behaviour should have been redirected to the quarkus realm login URL
be to
Regards,
Oliver

@sberyozkin
Copy link
Member Author

@orivat Thanks, this is actually what I'm seeing as well - I could not make Keycloak produce a backchannel logout request. We'll look with @pedroigor at it.
If you look at the PR you can see the test where the logout token is produced by the test itself works - the only difference is that with the real Keycloak I could not get the backchannel request coming in.

You have mentioned you had it working with WildFly - can you please try to use the same Keycloak configuration you use there ?
It appears to be a Keycloak setup issue...

@tassadar81
Copy link

@orivat Thanks, this is actually what I'm seeing as well - I could not make Keycloak produce a backchannel logout request. We'll look with @pedroigor at it. If you look at the PR you can see the test where the logout token is produced by the test itself works - the only difference is that with the real Keycloak I could not get the backchannel request coming in.

You have mentioned you had it working with WildFly - can you please try to use the same Keycloak configuration you use there ? It appears to be a Keycloak setup issue...

Did you configure the backchannel url into the realm?

@sberyozkin
Copy link
Member Author

@tassadar81 I've set it directly in Keycloak admin console, in the quarkus realm (the one I used for testing), in the client properties

@sberyozkin
Copy link
Member Author

sberyozkin commented Apr 4, 2022

@orivat See @tassadar81's question, while testing, did you add a backchannel URL in Keycloak ?

@tassadar81
Copy link

tassadar81 commented Apr 4, 2022

@tassadar81 I've set it directly in Keycloak admin console, in the quarkus realm (the one I used for testing), in the client properties

@sberyozkin ok and then you called the logout endpoint as specified in the well known configuration isn't it? That's the path i was using when i opened the #23477; so i think you should get a backchannel logout as i got it

@sberyozkin
Copy link
Member Author

@tassadar81

@sberyozkin ok and then you called the logout endpoint as specified in the well known configuration isn't it? That's the path i was using when i opened the #23477; so i think you should get a backchannel logout as i got it

In my local test (I modified quickstarts/security-openid-connect-web-authentication-quickstart) I configured the endpoint to support RP initiated logout which worked, the user was logged out - this RP initiated logout does go to the end session Keycloak endpoint.

But no backchannel request happened.

Perhaps it is because only a single endpoint is used ?

@tassadar81
Copy link

tassadar81 commented Apr 4, 2022

@tassadar81

@sberyozkin ok and then you called the logout endpoint as specified in the well known configuration isn't it? That's the path i was using when i opened the #23477; so i think you should get a backchannel logout as i got it

In my local test (I modified quickstarts/security-openid-connect-web-authentication-quickstart) I configured the endpoint to support RP initiated logout which worked, the user was logged out - this RP initiated logout does go to the end session Keycloak endpoint.

But no backchannel request happened.

Perhaps it is because only a single endpoint is used ?

@sberyozkin Well i'm not sure i've understood what you did, i'm trying to explain myself better:

the logout i was talking about the the one specified in the well known configuration:

http://mykeycloakinstance/auth/realms/myrealm/.well-known/openid-configuration
http://mykeycloakinstance/auth/realms/myrealm/protocol/openid-connect/logout

In the realm i guess you have configured this one in the "Backchannel Logout URL" client configuration:

http://localhost:8080/myquarkusapp/back-channel-logout

Which it will be called by keycloak once openid-connect/logout has called providing a jwt. No RP logout has to be called. Is it what you did?

If is what you did maybe a callback is issued but the jwt authentication fails?

@sberyozkin
Copy link
Member Author

sberyozkin commented Apr 4, 2022

@tassadar81 I was running an application endpoint in the debug mode with a breakpoint in the Vert.x handler.

So, as far as the end session Keycloak endpoint is concerned, I've tried 2 options:

  • calling it by initiating a logout from the endpoint - a user is redirected to this end session Keycloak endpoint
  • Logging out a user directly from a Keycloak console (I was hoping it would cause a backchannel request)

How do you call that endpoint ? Do you mean you direct the browser to it yourself ? Or from curl ?

(Yes this is the end session URL which is returned in the well known config that an RP initiated logout redirects the users to)

@sberyozkin sberyozkin force-pushed the oidc_back_channel_logout branch from eb40be8 to a28d221 Compare April 4, 2022 15:42
@sberyozkin
Copy link
Member Author

@tassadar81 Can you please create a simple reproducer project, with a Keycloak realm configured to support the backchannel URL which will point to a Quarkus JAX-RS endpoint, and then show how you call the KC logout endpoint, i.e, so that even without this PR we could see that this JAX-RS endpoint gets the backchannel request (it will have no effect in this case, - but I'd like to see what is it in your setup that makes it work), thanks

@sberyozkin sberyozkin force-pushed the oidc_back_channel_logout branch from a28d221 to 203e920 Compare April 11, 2022 17:25
@sberyozkin sberyozkin marked this pull request as ready for review April 11, 2022 17:54
@sberyozkin
Copy link
Member Author

sberyozkin commented Apr 11, 2022

Hi @pedroigor Thanks for the hint re calling out to Quarkus from a Docker hosted Keycloak - I did not get it myself in time that it won't work with localhost :-).
I've added a token-age property to support the logout tokens without the exp claims (for now it is only possible for the logout tokens). Please see the list of all the main changes in the description.

I have tested today that it also works with live Keycloak sending a backchannel logout token - token signature is verified, iat is checked against the token age, and then when the user returns, the IdToken is compared against the logout token (sid, etc). Unfortunately it would be very hard to test such a flow as I had to modify some code just to confirm it works with RP-initiated logout leading to the backchannel request (specifically, I commented out the code in CodeAuthenticationMech in order to confirm that after a user got logged out with RP Initiated logout the session cookie is not removed - to debug that indeed a pending backchannel state is compared against the ID token before the session cookie is removed).

I think the test added with this PR is quite close to the reality - the only difference is that the logout token is produced by the test code, the rest would be quite similar (same keys are used to sign both ID and logout tokens, etc).

In general I think it would be indeed a good enhancement for the users of Keycloak (and other OIDC providers which support it) to start adding the options to logout the users from all of the current applications... Thanks for the various hints

Let me know what you think may need to be changed
thanks

@quarkus-bot

This comment has been minimized.

@sberyozkin
Copy link
Member Author

Sorry I was checking the specific test related in this test suite and ignored the rest - looks like they have been affected by the new test configuration, will have a look tomorrow

* if the logout token contains it.
*/
@ConfigItem
public Optional<Duration> age = Optional.empty();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't make more sense to have this property available only for the backchannel logout config category?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about for a moment but then decided it could of common interest, the same extra restriction (that this token was not created 6 months ago :-) ) can be applied to bearer or even code flow tokens (smallrye-jwt has it and MP-JWT 2.1 will have it too).

@sberyozkin sberyozkin force-pushed the oidc_back_channel_logout branch from 203e920 to bc4706d Compare April 12, 2022 13:30
@sberyozkin
Copy link
Member Author

@pedroigor I've moved the backchannel logout config group under the existing logout one, it is looking OK, thanks.
Also dropped the ref to the KC to Quarkus authentication - it is supported at the Quarkus level if even needed in any case

@sberyozkin
Copy link
Member Author

CI is looking not bad so far, JVM 11 Windows test are quite unstable: quarkus-reactive-routes-deployment has failed

@quarkus-bot
Copy link

quarkus-bot bot commented Apr 12, 2022

Failing Jobs - Building bc4706d

Status Name Step Failures Logs Raw logs
✔️ JVM Tests - JDK 11
JVM Tests - JDK 11 Windows Build Failures Logs Raw logs
✔️ JVM Tests - JDK 17

Full information is available in the Build summary check run.

Failures

⚙️ JVM Tests - JDK 11 Windows #

- Failing: extensions/reactive-routes/deployment 
! Skipped: extensions/agroal/deployment extensions/elytron-security-jdbc/deployment extensions/flyway/deployment and 183 more

📦 extensions/reactive-routes/deployment

io.quarkus.vertx.web.context.DuplicatedContextTest.testThatRoutesAreCalledOnDuplicatedContext line 58 - More details - Source on GitHub

io.smallrye.mutiny.TimeoutException
	at io.smallrye.mutiny.operators.uni.UniBlockingAwait.await(UniBlockingAwait.java:64)
	at io.smallrye.mutiny.groups.UniAwait.atMost(UniAwait.java:65)

Copy link
Contributor

@pedroigor pedroigor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@sberyozkin
Copy link
Member Author

Thanks @pedroigor

@sberyozkin sberyozkin merged commit ed287d7 into quarkusio:main Apr 13, 2022
@quarkus-bot quarkus-bot bot added this to the 2.9 - main milestone Apr 13, 2022
@quarkus-bot quarkus-bot bot added the kind/enhancement New feature or request label Apr 13, 2022
@sberyozkin sberyozkin deleted the oidc_back_channel_logout branch April 13, 2022 12:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Quarkus oidc integration, backchannel logout support
4 participants