Skip to content

Commit

Permalink
Merge pull request #22163 from sberyozkin/oidc_session
Browse files Browse the repository at this point in the history
Add OidcSession interface
  • Loading branch information
sberyozkin authored Dec 20, 2021
2 parents 7c71329 + 3860a07 commit 9a92ad5
Show file tree
Hide file tree
Showing 19 changed files with 424 additions and 130 deletions.
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

0 comments on commit 9a92ad5

Please sign in to comment.