-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
Implement session logout in OAuth2Authenticator #18753
Implement session logout in OAuth2Authenticator #18753
Conversation
db55ab9
to
591f8f8
Compare
@@ -34,6 +34,8 @@ Response getOAuth2Response(String code, URI callbackUri, Optional<String> nonce) | |||
Response refreshTokens(String refreshToken) | |||
throws ChallengeFailedException; | |||
|
|||
Optional<URI> getLogoutEndPoint(Optional<String> idToken, URI callbackUrl); |
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.
nit: endpoint
is one word so casing seems off
@@ -114,6 +115,7 @@ private OAuth2ServerConfig readConfiguration(String body) | |||
else { | |||
userinfoEndpoint = Optional.empty(); | |||
} | |||
Optional<String> endSessionEndpoint = getOptionalField("end_session_endpoint", Optional.ofNullable(metadata.getEndSessionEndpointURI()).map(URI::toString), END_SESSION_URL, Optional.empty()); |
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.
It's required according to the spec:
end_session_endpoint
REQUIRED. URL at the OP to which an RP can perform a redirect to request that the End-User be logged out at the OP.
.build(); | ||
NonceCookie.delete()); | ||
if (oauth2Response.getIdToken().isPresent()) { | ||
builder.cookie(OAuthIdTokenCookie.create(oauth2Response.getIdToken().get(), oauth2Response.getExpiration())); |
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.
Shouldn't expiration be calculated as for OAuthWebUiCookie
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.
But in case of OAuthWebUiCookie
we do wrap the token as JWT and pass the expiration time for that, which is independent of the ID token. So shouldn't we use them ?
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. How this should work for refresh tokens in that case? I think we will get expiration
for the access token, let say, 1h
but since we will have refresh token we will be able to prolong the session for much longer than that. Using the same expiration for ID token cookie lifetime will mean that the this cookie will expire after 1h
and we won't be able to perform logout correctly after that.
core/trino-main/src/test/java/io/trino/server/security/TestResourceSecurity.java
Outdated
Show resolved
Hide resolved
@@ -1367,6 +1375,29 @@ private static Principal authenticate2(String user, String password) | |||
throw new AccessDeniedException("Invalid credentials2"); | |||
} | |||
|
|||
private static HttpCookie getWebUiOAuthCookie(CookieManager cookieManager) | |||
{ | |||
return cookieManager.getCookieStore().getCookies().stream() |
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.
nit: duplicated fragment, you could extract a method to get the cookie
@@ -836,7 +842,31 @@ public void testCustomPrincipalField() | |||
} | |||
} | |||
|
|||
private void assertAuth2Authentication(TestingTrinoServer server, String accessToken, boolean refreshTokensEnabled) | |||
@Test | |||
public void testOAuth2AuthenticatorWithoutEndsessionEndpoint() |
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.
What if http-server.authentication.oauth2.scopes
does not contain openid
scope but IdP returned end_session_endpoint
and the other way around?
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.
IIUC, in case of openid
scope then the end_session_endopoint is mandatory right ? But if the scope if non opeid
then we could optionally provide the end-point which if configured might be redirected. Am I missing something here ?
591f8f8
to
bb3cc23
Compare
@lukasz-walkiewicz Thanks for the review. Have addressed them |
a60c7f4
to
53d064a
Compare
.path("logout.html") | ||
.build(); | ||
|
||
return Response.seeOther(auth2Client.getLogoutEndpoint(idToken, callBackUri).orElse(callBackUri)) |
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 it possible to call sso logout ourselves behind the scenes instead of depending on user browser's doing that?
Otherwise we could end up in situation where the user doesn't follow that redirect for any reason and the value is still valid.
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 spec here mentions that
An RP requests that the OP log out the End-User by redirecting the End-User's User Agent to the OP's Logout Endpoint. This URL is normally obtained via the end_session_endpoint element of the OP's Discovery response or may be learned via other mechanisms.
So I guess we should redirect on the User browser instead of handling it. Additionally each IdP can have its own way to redirecting the user right ?
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.
Overall, looks good but I think we lack a bit of coverage, especially wrt support for refresh tokens.
core/trino-main/src/main/java/io/trino/server/ui/OAuth2WebUiAuthenticationFilter.java
Outdated
Show resolved
Hide resolved
.build(); | ||
NonceCookie.delete()); | ||
if (oauth2Response.getIdToken().isPresent()) { | ||
builder.cookie(OAuthIdTokenCookie.create(oauth2Response.getIdToken().get(), oauth2Response.getExpiration())); |
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. How this should work for refresh tokens in that case? I think we will get expiration
for the access token, let say, 1h
but since we will have refresh token we will be able to prolong the session for much longer than that. Using the same expiration for ID token cookie lifetime will mean that the this cookie will expire after 1h
and we won't be able to perform logout correctly after that.
...rc/test/java/io/trino/server/security/oauth2/TestOAuth2WebUiAuthenticationFilterWithJwt.java
Show resolved
Hide resolved
7a647ed
to
9fcc9d0
Compare
@lukasz-walkiewicz Thanks a lot for the review. AC |
9fcc9d0
to
0ac0b57
Compare
@@ -114,6 +115,7 @@ private OAuth2ServerConfig readConfiguration(String body) | |||
else { | |||
userinfoEndpoint = Optional.empty(); | |||
} | |||
Optional<URI> endSessionEndpoint = Optional.of(getRequiredField("end_session_endpoint", metadata.getEndSessionEndpointURI(), END_SESSION_URL, Optional.empty())); |
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.
Should provide an override value for END_SESSION_URL which comes from the OidcDiscoveryConfig. Similar to the other URL values. This allows non-standard endpoint 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.
That's a good point but OidcDiscoveryConfig
actually allows providing urls explicitly (effectively overriding those obtained from metadata discovery) for backward compatibility, allowing for non-standard endpoint values wasn't the objective. We intend to drop these properties #18101
That said, is it something you think we should support? Is this something you use these properties 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.
This END_SESSION_URL, Optional.empty()) becomes this END_SESSION_URL, logoutUrl), where logoutUrl is set in the constructor
logoutUrl = requireNonNull(oidcConfig.getEndSessionUrl(), "logoutUrl is null");
I had to make these changes for a customer, who is using Ping. The "standard" endpoint found with end_session_endpoint is not being found because the well-known endpoint is returning ping_end_session_endpoint. So the customer had to be able to specify the logout URL explicitly.
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.
But if we disable the OIDC discovery then we could inject them via StaticConfigurationProvider
- we could provide custom end-points right ? cc : @lukasz-walkiewicz Are we removing that endpoint also ?
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.
No, StaticConfigurationProvider
is here to stay. I think we ultimately would like to remove this overriding some day and users should provide configuration explicitly if the IdP does not conform OIDC spec.
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.
@thorbjornsen In this case we could still use StaticOAuth2ServerConfiguration
but we need to specify the configuration explicitly.
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 % minor changes to the docs
@@ -175,7 +177,8 @@ public void createClient( | |||
"--response-types", "token,code,id_token", | |||
"--scope", "openid,offline", | |||
"--token-endpoint-auth-method", tokenEndpointAuthMethod.getValue(), | |||
"--callbacks", callbackUrl) | |||
"--callbacks", callbackUrl, | |||
"--post-logout-callbacks", logoutCallbackUrl) |
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.
Probably we need to update docs to add information about registering post-logout url
@@ -114,6 +115,7 @@ private OAuth2ServerConfig readConfiguration(String body) | |||
else { | |||
userinfoEndpoint = Optional.empty(); | |||
} | |||
Optional<URI> endSessionEndpoint = Optional.of(getRequiredField("end_session_endpoint", metadata.getEndSessionEndpointURI(), END_SESSION_URL, Optional.empty())); |
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.
That's a good point but OidcDiscoveryConfig
actually allows providing urls explicitly (effectively overriding those obtained from metadata discovery) for backward compatibility, allowing for non-standard endpoint values wasn't the objective. We intend to drop these properties #18101
That said, is it something you think we should support? Is this something you use these properties for?
0ac0b57
to
a97d69f
Compare
@mosabua Can you please check the docs. |
@lukasz-walkiewicz Updated the docs |
@@ -29,14 +29,16 @@ class OAuth2ServerConfig | |||
private final URI tokenUrl; |
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 could be record
. Maybe follow up?
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 added it as a prep commit
a97d69f
to
8309405
Compare
Extract cookieExpiration time computation
If the NodeInternalServer is not set, we tend to pick a random interface from getNetworkInterfaces
- Extracted cookie attribute assertion - Rename assertTrinoCookie to assertTrinoOAuth2Cookie - Extract parseJwsClaims from TestOAuth2WebUiAuthenticationFilterWithJwt to BaseOAuth2WebUiAuthenticationFilterTest
8309405
to
370f00b
Compare
4df11c9
to
b904eb0
Compare
@mosabua AC |
When trying to logout from Trino WebUI, allows the user to be redirected to a specific end_session_endpoint thereby allowing user to logout from the IdP/OP. Specification - https://openid.net/specs/openid-connect-rpinitiated-1_0.html
b904eb0
to
e066e29
Compare
@mosabua AC |
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.
Docs look good now.
Description
When trying to logout from Trino WebUI, allows the user to be redirected to a specific end_session_endpoint thereby allowing user to logout from the IdP/OP.
Specification - https://openid.net/specs/openid-connect-rpinitiated-1_0.html
Will be sharing a video link on how it worked before and after this change.
Fixes #13060
Additional context and related issues
Release notes
( ) This is not user-visible or is docs only, and no release notes are required.
(x) Release notes are required. Please propose a release note for me.
( ) Release notes are required, with the following suggested text: