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 OidcSession interface #22163

Merged
merged 1 commit into from
Dec 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ import io.vertx.ext.web.RoutingContext;
@ApplicationScoped
public class CustomTenantResolver implements TenantResolver {

@Override
@Override
public String resolve(RoutingContext context) {
String path = context.request().path();
String[] parts = path.split("/");
Expand All @@ -152,6 +152,45 @@ public class CustomTenantResolver implements TenantResolver {

From the implementation above, tenants are resolved from the request path so that in case no tenant could be inferred, `null` is returned to indicate that the default tenant configuration should be used.

[NOTE]
===
When a current tenant represents an OIDC `web-app` application, the current `io.vertx.ext.web.RoutingContext` will contain a `tenant-id` attribute by the time the custom tenant resolver has been called for all the requests completing the code authentication flow and the already authenticated requests, when either a tenant specific state or session cookie already exists.
Therefore, when working with mulltiple OpenId Connect Providers, you only need a path specific check to resolve a tenant id if the `RoutingContext` does not have the `tenant-id` attribute set, for example:

[source,java]
----
package org.acme.quickstart.oidc;

import javax.enterprise.context.ApplicationScoped;

import io.quarkus.oidc.TenantResolver;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
public class CustomTenantResolver implements TenantResolver {

@Override
public String resolve(RoutingContext context) {
String tenantId = context.get("tenant-id");
if (tenantId != null) {
return tenantId;
} else {
// Initial login request
String path = context.request().path();
String[] parts = path.split("/");

if (parts.length == 0) {
// resolve to default tenant configuration
return null;
}
return parts[1];
}
}
}
----

===

[NOTE]
===
If you also use xref:hibernate-orm.adoc#multitenancy[Hibernate ORM multitenancy] and both OIDC and Hibernate ORM tenant IDs are the same and must be extracted from the Vert.x `RoutingContext` then you can pass the tenant id from the OIDC Tenant Resolver to the Hibernate ORM Tenant Resolver as a `RoutingContext` attribute, for example:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,34 @@ quarkus.oidc.logout.post-logout-uri-param=returnTo
quarkus.oidc.logout.extra-params.client_id=${quarkus.oidc.client-id}
----

[[local-logout]]
==== Local Logout

If you work with a social provider such as Google and are concerned that the users can be logged out from all their Google applications with the <<user-initiated-logout,User-Initiated Logout>> which redirects the users to the provider's logout endpoint then you can support a local logout with the help of the <<oidc-session,OidcSession>> which only clears the local session cookie, for example:

[source,java]
----
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

import io.quarkus.oidc.OidcSession;

@Path("/service")
public class ServiceResource {

@Inject
OidcSession oidcSession;

@GET
@Path("logout")
public String logout() {
oidcSession.logout().await().indefinitely();
return "You are logged out".
}
----


[[session-management]]
=== Session Management

Expand All @@ -495,6 +523,11 @@ You can have this process further optimized by having a simple JavaScript functi

Note this user session can not be extended forever - the returning user with the expired ID token will have to re-authenticate at the OIDC provider endpoint once the refresh token has expired.

[[oidc-session]]
==== OidcSession

`io.quarkus.oidc.OidcSession` is a wrapper around the current `IdToken`. It can help to perform a <<local-logout, Local Logout>>, retrieve the current session's tenant identifier and check when the session will expire. More useful methods will be added to it over time.

==== TokenStateManager

OIDC `CodeAuthenticationMechanism` is using the default `io.quarkus.oidc.TokenStateManager` interface implementation to keep the ID, access and refresh tokens returned in the authorization code or refresh grant responses in a session cookie. It makes Quarkus OIDC endpoints completely stateless.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import io.quarkus.oidc.runtime.OidcIdentityProvider;
import io.quarkus.oidc.runtime.OidcJsonWebTokenProducer;
import io.quarkus.oidc.runtime.OidcRecorder;
import io.quarkus.oidc.runtime.OidcSessionImpl;
import io.quarkus.oidc.runtime.OidcTokenCredentialProducer;
import io.quarkus.oidc.runtime.TenantConfigBean;
import io.quarkus.runtime.TlsConfig;
Expand Down Expand Up @@ -87,7 +88,8 @@ public void additionalBeans(BuildProducer<AdditionalBeanBuildItem> additionalBea
.addBeanClass(OidcConfigurationMetadataProducer.class)
.addBeanClass(OidcIdentityProvider.class)
.addBeanClass(DefaultTenantConfigResolver.class)
.addBeanClass(DefaultTokenStateManager.class);
.addBeanClass(DefaultTokenStateManager.class)
.addBeanClass(OidcSessionImpl.class);
additionalBeans.produce(builder.build());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.quarkus.oidc;

import java.time.Instant;

import org.eclipse.microprofile.jwt.JsonWebToken;

import io.smallrye.mutiny.Uni;

public interface OidcSession {

/**
* Return the tenant identifier of the current session
*
* @return tenant id
*/
String getTenantId();

/**
* Return an {@linkplain:Instant} indicating how long will it take for the current session to expire.
*
* @return
*/
Instant expiresIn();

/**
* Perform a local logout without a redirect to the OpenId Connect provider.
*
* @return Uni<Void>
*/
Uni<Void> logout();

/**
* Return the ID token the current session depends upon.
*
* @return id token
*/
JsonWebToken getIdToken();

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.quarkus.oidc.runtime;

import java.util.function.Function;

import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.quarkus.oidc.AccessTokenCredential;
Expand All @@ -21,18 +19,13 @@ public class BearerAuthenticationMechanism extends AbstractOidcAuthenticationMec
HttpHeaderNames.WWW_AUTHENTICATE, OidcConstants.BEARER_SCHEME);

public Uni<SecurityIdentity> authenticate(RoutingContext context,
IdentityProviderManager identityProviderManager) {
return resolver.resolveConfig(context).chain(new Function<OidcTenantConfig, Uni<? extends SecurityIdentity>>() {
@Override
public Uni<? extends SecurityIdentity> apply(OidcTenantConfig oidcTenantConfig) {
String token = extractBearerToken(context, oidcTenantConfig);
// if a bearer token is provided try to authenticate
if (token != null) {
return authenticate(identityProviderManager, context, new AccessTokenCredential(token));
}
return Uni.createFrom().nullItem();
}
});
IdentityProviderManager identityProviderManager, OidcTenantConfig oidcTenantConfig) {
String token = extractBearerToken(context, oidcTenantConfig);
// if a bearer token is provided try to authenticate
if (token != null) {
return authenticate(identityProviderManager, context, new AccessTokenCredential(token));
}
return Uni.createFrom().nullItem();
}

public Uni<ChallengeData> getChallenge(RoutingContext context) {
Expand Down
Loading