-
Notifications
You must be signed in to change notification settings - Fork 128
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Supports Endsession endpoint for microsoft login (#1876)
- Loading branch information
Showing
8 changed files
with
207 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
...io/micronaut/security/oauth2/endpoint/endsession/request/MicrosoftEndSessionEndpoint.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/* | ||
* Copyright 2017-2023 original authors | ||
* | ||
* Licensed 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 | ||
* | ||
* https://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 io.micronaut.security.oauth2.endpoint.endsession.request; | ||
|
||
import io.micronaut.core.annotation.Internal; | ||
import io.micronaut.http.HttpRequest; | ||
import io.micronaut.security.authentication.Authentication; | ||
import io.micronaut.security.oauth2.client.OpenIdProviderMetadata; | ||
import io.micronaut.security.oauth2.configuration.OauthClientConfiguration; | ||
import io.micronaut.security.oauth2.endpoint.endsession.response.EndSessionCallbackUrlBuilder; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.function.Supplier; | ||
|
||
/** | ||
* An {@link EndSessionEndpoint} which redirects to the Microsoft end session endpoint obtained via the OpenID Connect Configuration. | ||
* It adds two query value parameters {@code post_logout_redirect_uri} and {@code logout_hint}. | ||
* {@code post_logout_redirect_uri} The URL that the user is redirected to after successfully signing out. If the parameter isn't included, the user is shown a generic message that's generated by the Microsoft identity platform. This URL must match one of the redirect URIs registered for your application in the app registration portal. | ||
* {@code logout_hint} Enables sign-out to occur without prompting the user to select an account. To use logout_hint, enable the login_hint optional claim in your client application and use the value of the login_hint optional claim as the logout_hint parameter. | ||
* <a href="https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc#send-a-sign-out-request">Send a sign-out request</a> | ||
*/ | ||
@Internal | ||
final class MicrosoftEndSessionEndpoint extends AbstractEndSessionRequest { | ||
private static final String PARAM_POST_LOGOUT_REDIRECT_URI = "post_logout_redirect_uri"; | ||
private static final String CLAIM_LOGIN_HINT = "login_hint"; | ||
private static final String CLAIM_LOGOUT_HINT = "logout_hint"; | ||
|
||
/** | ||
* @param endSessionCallbackUrlBuilder The end session callback URL builder | ||
* @param clientConfiguration The client configuration | ||
* @param providerMetadata The provider metadata supplier | ||
*/ | ||
MicrosoftEndSessionEndpoint(EndSessionCallbackUrlBuilder endSessionCallbackUrlBuilder, | ||
OauthClientConfiguration clientConfiguration, | ||
Supplier<OpenIdProviderMetadata> providerMetadata) { | ||
super(endSessionCallbackUrlBuilder, clientConfiguration, providerMetadata); | ||
} | ||
|
||
@Override | ||
protected String getUrl() { | ||
return providerMetadataSupplier.get().getEndSessionEndpoint(); | ||
} | ||
|
||
@Override | ||
protected Map<String, Object> getArguments(HttpRequest<?> originating, | ||
Authentication authentication) { | ||
Map<String, Object> arguments = new HashMap<>(); | ||
arguments.put(PARAM_POST_LOGOUT_REDIRECT_URI, getRedirectUri(originating)); | ||
Object loginHint = authentication.getAttributes().get(CLAIM_LOGIN_HINT); | ||
if (loginHint != null) { | ||
arguments.put(CLAIM_LOGOUT_HINT, loginHint); | ||
} | ||
return arguments; | ||
} | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 121 additions & 0 deletions
121
...icronaut/security/oauth2/endpoint/endsession/request/MicrosoftEndSessionEndpointTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package io.micronaut.security.oauth2.endpoint.endsession.request; | ||
|
||
import io.micronaut.context.ApplicationContext; | ||
import io.micronaut.context.annotation.Replaces; | ||
import io.micronaut.context.annotation.Requires; | ||
import io.micronaut.http.HttpMethod; | ||
import io.micronaut.http.annotation.Controller; | ||
import io.micronaut.http.annotation.Get; | ||
import io.micronaut.http.simple.SimpleHttpRequest; | ||
import io.micronaut.http.uri.UriBuilder; | ||
import io.micronaut.inject.qualifiers.Qualifiers; | ||
import io.micronaut.runtime.server.EmbeddedServer; | ||
import io.micronaut.security.annotation.Secured; | ||
import io.micronaut.security.authentication.Authentication; | ||
import io.micronaut.security.oauth2.client.OpenIdClient; | ||
import io.micronaut.security.oauth2.client.OpenIdProviderMetadata; | ||
import io.micronaut.security.oauth2.configuration.OauthClientConfiguration; | ||
import io.micronaut.security.oauth2.endpoint.endsession.response.EndSessionCallbackUrlBuilder; | ||
import io.micronaut.security.rules.SecurityRule; | ||
import jakarta.inject.Singleton; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import java.net.URI; | ||
import java.util.Collections; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
class MicrosoftEndSessionEndpointTest { | ||
private static final String LOGOUT = "https://login.microsoftonline.com/8177030d-4c56-3c4a-a111-15a102c55cba/oauth2/v2.0/logout"; | ||
private static final String OPENID_CONFIG = """ | ||
{ | ||
"token_endpoint":"https://login.microsoftonline.com/8177030d-4c56-3c4a-a111-15a102c55cba/oauth2/v2.0/token", | ||
"token_endpoint_auth_methods_supported":["client_secret_post","private_key_jwt","client_secret_basic"], | ||
"jwks_uri":"https://login.microsoftonline.com/8177030d-4c56-3c4a-a111-15a102c55cba/discovery/v2.0/keys", | ||
"response_modes_supported":["query","fragment","form_post"], | ||
"subject_types_supported":["pairwise"], | ||
"id_token_signing_alg_values_supported":["RS256"], | ||
"response_types_supported":["code","id_token","code id_token","id_token token"], | ||
"scopes_supported":["openid","profile","email","offline_access"], | ||
"issuer":"https://login.microsoftonline.com/8177030d-4c56-3c4a-a111-15a102c55cba/v2.0", | ||
"request_uri_parameter_supported":false, | ||
"userinfo_endpoint":"https://graph.microsoft.com/oidc/userinfo", | ||
"authorization_endpoint":"https://login.microsoftonline.com/8177030d-4c56-3c4a-a111-15a102c55cba/oauth2/v2.0/authorize", | ||
"device_authorization_endpoint":"https://login.microsoftonline.com/8177030d-4c56-3c4a-a111-15a102c55cba/oauth2/v2.0/devicecode", | ||
"http_logout_supported":true, | ||
"frontchannel_logout_supported":true, | ||
"end_session_endpoint":"https://login.microsoftonline.com/8177030d-4c56-3c4a-a111-15a102c55cba/oauth2/v2.0/logout", | ||
"claims_supported":["sub","iss","cloud_instance_name","cloud_instance_host_name","cloud_graph_host_name","msgraph_host","aud","exp","iat","auth_time","acr","nonce","preferred_username","name","tid","ver","at_hash","c_hash","email"], | ||
"kerberos_endpoint":"https://login.microsoftonline.com/8177030d-4c56-3c4a-a111-15a102c55cba/kerberos", | ||
"tenant_region_scope":null, | ||
"cloud_instance_name":"microsoftonline.com", | ||
"cloud_graph_host_name":"graph.windows.net", | ||
"msgraph_host":"graph.microsoft.com", | ||
"rbac_url":"https://pas.windows.net" | ||
}"""; | ||
|
||
@Test | ||
void oracleCloudConfigurationSupportsEndSession() { | ||
String nameQualifier = "microsoft"; | ||
try (EmbeddedServer authServer = ApplicationContext.run(EmbeddedServer.class, | ||
Map.of("spec.name", "MicrosoftEndSessionEndpointTestAuthServer"))) { | ||
try (EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class, | ||
Map.of("spec.name", "MicrosoftEndSessionEndpointTest", | ||
"micronaut.security.oauth2.clients." + nameQualifier + ".openid.issuer", authServer.getURL().toString(), | ||
"micronaut.security.oauth2.clients." + nameQualifier + ".client-secret", "yyy", | ||
"micronaut.security.oauth2.clients." + nameQualifier + ".client-id", "xxx" | ||
))) { | ||
var openIdClient = server.getApplicationContext().getBean(OpenIdClient.class, Qualifiers.byName(nameQualifier)); | ||
assertTrue(openIdClient.supportsEndSession()); | ||
var endSessionEndpointResolver = server.getApplicationContext().getBean(EndSessionEndpointResolver.class); | ||
var oauthClientConfiguration = server.getApplicationContext().getBean(OauthClientConfiguration.class, Qualifiers.byName(nameQualifier)); | ||
var openIdProviderMetadata = server.getApplicationContext().getBean(OpenIdProviderMetadata.class); | ||
var endSessionCallbackUrlBuilder = server.getApplicationContext().getBean(EndSessionCallbackUrlBuilder.class); | ||
Optional<EndSessionEndpoint> endSessionEndpointOptional = endSessionEndpointResolver.resolve(oauthClientConfiguration, openIdProviderMetadata, endSessionCallbackUrlBuilder); | ||
assertTrue(endSessionEndpointOptional.isPresent()); | ||
EndSessionEndpoint endSessionEndpoint = endSessionEndpointOptional.get(); | ||
|
||
// if no login_hint is provided, only post_logout_redirect_uri is added | ||
Authentication authentication = Authentication.build("sherlock"); | ||
String url = endSessionEndpoint.getUrl(new SimpleHttpRequest<>(HttpMethod.GET, "/foo/bar", Collections.emptyMap()), authentication); | ||
String expected = UriBuilder.of(LOGOUT) | ||
.queryParam("post_logout_redirect_uri", "http://localhost:"+ server.getPort() + "/logout") | ||
.build() | ||
.toString(); | ||
assertEquals(expected, url); | ||
|
||
// if login_hint is provided, logout_hint is added | ||
authentication = Authentication.build("sherlock", Collections.singletonMap("login_hint", "xyz")); | ||
url = endSessionEndpoint.getUrl(new SimpleHttpRequest<>(HttpMethod.GET, "/foo/bar", Collections.emptyMap()), authentication); | ||
URI expectedURI = UriBuilder.of(LOGOUT) | ||
.queryParam("logout_hint", "xyz") | ||
.queryParam("post_logout_redirect_uri", "http://localhost:"+ server.getPort() + "/logout") | ||
.build(); | ||
assertEquals(expectedURI, URI.create(url)); | ||
} | ||
} | ||
} | ||
|
||
@Requires(property = "spec.name", value = "MicrosoftEndSessionEndpointTest") | ||
@Singleton | ||
@Replaces(AuthorizationServerResolver.class) | ||
static class AuthorizationServerResolverReplacement implements AuthorizationServerResolver { | ||
@Override | ||
public Optional<AuthorizationServer> resolve(String issuer) { | ||
return Optional.of(AuthorizationServer.MICROSOFT); | ||
} | ||
} | ||
|
||
@Requires(property = "spec.name", value = "MicrosoftEndSessionEndpointTestAuthServer") | ||
@Controller | ||
static class OpenidConfigurationController { | ||
@Secured(SecurityRule.IS_ANONYMOUS) | ||
@Get("/.well-known/openid-configuration") | ||
String index() { | ||
return OPENID_CONFIG; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters