Skip to content

Commit

Permalink
Introduce OidcRedirectFilter
Browse files Browse the repository at this point in the history
  • Loading branch information
sberyozkin committed May 13, 2024
1 parent 77cf0a8 commit bd52f6f
Show file tree
Hide file tree
Showing 11 changed files with 284 additions and 28 deletions.
123 changes: 123 additions & 0 deletions docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ For example, `quarkus.oidc.authentication.redirect-path=/service/callback`, and
If `quarkus.oidc.authentication.redirect-path` is set, but you need the original request URL to be restored after the user is redirected back to a unique callback URL, for example, `http://localhost:8080/service/callback`, set `quarkus.oidc.authentication.restore-path-after-redirect` property to `true`.
This will restore the request URL such as `http://localhost:8080/service/1`.

[[customize-authentication-requests]]
==== Customizing authentication requests

By default, only the `response_type` (set to `code`), `scope` (set to `openid`), `client_id`, `redirect_uri`, and `state` properties are passed as HTTP query parameters to the OIDC provider's authorization endpoint when the user is redirected to it to authenticate.

Check warning on line 391 in docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'.", "location": {"path": "docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc", "range": {"start": {"line": 391, "column": 145}}}, "severity": "INFO"}
Expand All @@ -398,6 +399,8 @@ The following example shows how you can work around this issue:
quarkus.oidc.authentication.extra-params.response_mode=query
----

See also the <<oidc-redirect-filters>> section explaining how a custom `OidcRedirectFilter` can be used to customize OIDC redirects, including those to the OIDC authorization endpoint.

==== Customizing the authentication error response

When the user is redirected to the OIDC authorization endpoint to authenticate and, if necessary, authorize the Quarkus application, this redirect request might fail, for example, when an invalid scope is included in the redirect URI.

Check warning on line 406 in docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.SentenceLength] Try to keep sentences to an average of 32 words or fewer. Raw Output: {"message": "[Quarkus.SentenceLength] Try to keep sentences to an average of 32 words or fewer.", "location": {"path": "docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc", "range": {"start": {"line": 406, "column": 1}}}, "severity": "INFO"}
Expand All @@ -422,6 +425,124 @@ For example, if it is set to '/error' and the current request URI is `https://lo
To prevent the user from being redirected to this page to be re-authenticated, ensure that this error endpoint is a public resource.
====

[[oidc-redirect-filters]]
=== OIDC redirect filters

You can register one or more `io.quarkus.oidc.OidcRedirectFilter` implementations to filter OIDC redirects to OIDC authorization and logout endpoints but also local redirects to custom error and session expired pages. Custom `OidcRedirectFilter` can add additional query parameters, response headers and set new cookies.

For example, the following simple custom `OidcRedirectFilter` adds an additional query parameter and a custom response header for all redirect requests that can be done by Quarkus OIDC:

[source,java]
----
package io.quarkus.it.keycloak;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.OidcRedirectFilter;
import io.quarkus.oidc.OidcTenantConfig;
import io.vertx.ext.web.RoutingContext;
@ApplicationScoped
@Unremovable
public class CustomOidcRedirectFilter implements OidcRedirectFilter {
@Override
public String filter(RoutingContext routingContext, OidcTenantConfig oidcConfig, String redirectUri) {
routingContext.response().putHeader("Redirect-Filtered", "true");
return redirectUri + "?redirect-filtered=true";
}
}
----

See also the <<customize-authentication-requests>> section how to configure additional query parameters for OIDC authorization point.

Custom `OidcRedirectFilter` for local error and session expired pages can also create secure cookies to help with generating such pages.

For example, let's assume you need to redirect the current user whose session has expired to a custom session expired page available at `http://localhost:8080/session-expired-page`. The following custom `OidcRedirectFilter` encrypts the user name in a custom `session_expired` cookie using an OIDC tenant client secret:

Check warning on line 462 in docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.SentenceLength] Try to keep sentences to an average of 32 words or fewer. Raw Output: {"message": "[Quarkus.SentenceLength] Try to keep sentences to an average of 32 words or fewer.", "location": {"path": "docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc", "range": {"start": {"line": 462, "column": 1}}}, "severity": "INFO"}

Check warning on line 462 in docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'.", "location": {"path": "docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc", "range": {"start": {"line": 462, "column": 31}}}, "severity": "INFO"}

Check warning on line 462 in docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc", "range": {"start": {"line": 462, "column": 284}}}, "severity": "INFO"}

[source,java]
----
package io.quarkus.it.keycloak;
import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.jwt.Claims;
import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.AuthorizationCodeTokens;
import io.quarkus.oidc.OidcRedirectFilter;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.runtime.OidcUtils;
import io.smallrye.jwt.build.Jwt;
import io.vertx.ext.web.RoutingContext;
@ApplicationScoped
@Unremovable
public class SessionExpiredOidcRedirectFilter implements OidcRedirectFilter {
@Override
public String filter(RoutingContext routingContext, OidcTenantConfig oidcConfig, String redirectUri) {
if (redirectUri.endsWith("/session-expired-page")) {
AuthorizationCodeTokens tokens = routingContext.get(AuthorizationCodeTokens.class.getName()); <1>
String userName = OidcUtils.decodeJwtContent(tokens.getIdToken()).getString(Claims.preferred_username.name()); <2>
String jwe = Jwt.preferredUserName(userName).jwe().encryptWithSecret(oidcConfig.credentials.secret.get()); <3>
OidcUtils.createCookie(routingContext, oidcConfig, "session_expired", jwe + "|" + oidcConfig.tenantId.get(), 5); <4>
}
return redirectUri;
}
}
----
<1> Access `AuthorizationCodeTokens` tokens associated with the now expired session as a `RoutingContext` attribute.
<2> Decode ID token claims and get a user name.
<3> Save the user name in a JWT token encrypted with the current OIDC tenant's client secret.
<4> Create a custom `session_expired` cookie valid for 5 seconds which joins the encrypted token and a tenant id using a "|" separator. Recording a tenant id in a custom cookie can help to generate correct session expired pages in a multi-tenant OIDC setup.

Check warning on line 499 in docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using ', which (non restrictive clause preceded by a comma)' or 'that (restrictive clause without a comma)' rather than 'which'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using ', which (non restrictive clause preceded by a comma)' or 'that (restrictive clause without a comma)' rather than 'which'.", "location": {"path": "docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc", "range": {"start": {"line": 499, "column": 65}}}, "severity": "INFO"}

Check warning on line 499 in docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc", "range": {"start": {"line": 499, "column": 113}}}, "severity": "INFO"}

Next, a public JAX-RS resource which generates session expired pages can use this cookie to create a page tailored for this user and the corresponding OIDC tenant, for example:

Check warning on line 501 in docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using ', which (non restrictive clause preceded by a comma)' or 'that (restrictive clause without a comma)' rather than 'which'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using ', which (non restrictive clause preceded by a comma)' or 'that (restrictive clause without a comma)' rather than 'which'.", "location": {"path": "docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc", "range": {"start": {"line": 501, "column": 31}}}, "severity": "INFO"}

[source,java]
----
package io.quarkus.it.keycloak;
import jakarta.inject.Inject;
import jakarta.ws.rs.CookieParam;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.jwt.Claims;
import org.eclipse.microprofile.jwt.JsonWebToken;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.runtime.TenantConfigBean;
import io.smallrye.jwt.auth.principal.DefaultJWTParser;
import io.vertx.ext.web.RoutingContext;
@Path("/session-expired-page")
public class SessionExpiredResource {
@Inject
TenantConfigBean tenantConfig; <1>
@GET
public String sessionExpired(@CookieParam("session_expired") String sessionExpired) throws Exception {
// Cookie format: jwt|<tenant id>
String[] pair = sessionExpired.split("\\|"); <2>
OidcTenantConfig oidcConfig = tenantConfig.getStaticTenantsConfig().get(pair[1]).getOidcTenantConfig(); <3>
JsonWebToken jwt = new DefaultJWTParser().decrypt(pair[0], oidcConfig.credentials.secret.get()); <4>
OidcUtils.removeCookie(context, oidcConfig, "session_expired"); <5>
return jwt.getClaim(Claims.preferred_username) + ", your session has expired. "
+ "Please login again at http://localhost:8081/" + oidcConfig.tenantId.get(); <6>
}
}
----
<1> Inject `TenantConfigBean` which can be used to access all the current OIDC tenant configurations.
<2> Split the custom cookie value into 2 parts, first part is the encrypted token, last part is the tenant id.
<3> Get the OIDC tenant configuration.
<4> Decrypt the cookie value using the OIDC tenant's client secret.

Check warning on line 542 in docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc", "range": {"start": {"line": 542, "column": 29}}}, "severity": "INFO"}
<5> Remove the custom cookie.
<6> Use the username in the decrypted token and the tenant id to generate the service expired page response.

=== Accessing authorization data

You can access information about authorization in different ways.
Expand Down Expand Up @@ -1110,6 +1231,8 @@ When the session can not be refreshed, the currently authenticated user is redir
Instead, you can request that the user is redirected to a public, application specific session expired page first. This page informs the user that the session has now expired and advise to re-authenticate by following a link to a secured application welcome page. The user clicks on the link and Quarkus OIDC enforces a redirect to the OIDC provider to re-authenticate. Use `quarkus.oidc.authentication.session-expired-page` relative path property, if you'd like to do it.
For example, setting `quarkus.oidc.authentication.session-expired-page=/session-expired-page` will ensure that the user whose session has expired is redirected to `http://localhost:8080/session-expired-page`, assuming the application is available at `http://localhost:8080`.

Check warning on line 1233 in docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'.", "location": {"path": "docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc", "range": {"start": {"line": 1233, "column": 136}}}, "severity": "INFO"}
See also the <<oidc-redirect-filters>> section explaining how a custom `OidcRedirectFilter` can be used to customize OIDC redirects, including those to the session expired pages.
====


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

import io.vertx.ext.web.RoutingContext;

/**
* OIDC redirect filter which can be used to customize redirect requests to OIDC authorization and logout endpoints
* as well as local redirects to OIDC tenant error, session expired and other pages.
*/
public interface OidcRedirectFilter {
/**
* Filter OIDC redirect.
*
* @param routingContext the routing context that can be used to set additional cookies.
* @param oidcConfig current OIDC tenant configuration.
* @param redirectUri the redirect uri which filters may update by adding additional query parameters.
* @param tokenClaims the decoded JWT token claims in JSON format. If necessary, implementations can convert it to JSON
* object.
* @return redirectUri the redirect uri which can have additional query parameters added.
*/
String filter(RoutingContext routingContext, OidcTenantConfig oidcConfig, String redirectUri);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import io.quarkus.oidc.AuthorizationCodeTokens;
import io.quarkus.oidc.IdTokenCredential;
import io.quarkus.oidc.JavaScriptRequestChecker;
import io.quarkus.oidc.OidcRedirectFilter;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.OidcTenantConfig.Authentication;
import io.quarkus.oidc.OidcTenantConfig.Authentication.ResponseMode;
Expand All @@ -52,7 +53,6 @@
import io.vertx.core.http.Cookie;
import io.vertx.core.http.CookieSameSite;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.impl.CookieImpl;
import io.vertx.core.http.impl.ServerCookie;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
Expand Down Expand Up @@ -227,8 +227,10 @@ public Uni<SecurityIdentity> apply(TenantConfigContext tenantContext) {

String finalErrorUri = errorUri.toString();
LOG.debugf("Error URI: %s", finalErrorUri);
return Uni.createFrom().failure(new AuthenticationRedirectException(finalErrorUri));
return Uni.createFrom().failure(new AuthenticationRedirectException(
filterRedirect(context, tenantContext, finalErrorUri)));
}

});
} else {
LOG.error(
Expand All @@ -242,6 +244,16 @@ public Uni<SecurityIdentity> apply(TenantConfigContext tenantContext) {

}

private static String filterRedirect(RoutingContext context,
TenantConfigContext tenantContext, String redirectUri) {
if (!tenantContext.getOidcRedirectFilters().isEmpty()) {
for (OidcRedirectFilter filter : tenantContext.getOidcRedirectFilters()) {
redirectUri = filter.filter(context, tenantContext.oidcConfig, redirectUri);
}
}
return redirectUri;
}

private Uni<SecurityIdentity> stateParamIsMissing(OidcTenantConfig oidcTenantConfig, RoutingContext context,
Map<String, Cookie> cookies, boolean multipleStateQueryParams) {
if (multipleStateQueryParams) {
Expand Down Expand Up @@ -432,7 +444,8 @@ private Uni<SecurityIdentity> redirectToSessionExpiredPage(RoutingContext contex
String sessionExpiredUri = sessionExpired.toString();
LOG.debugf("Session Expired URI: %s", sessionExpiredUri);
return removeSessionCookie(context, configContext.oidcConfig)
.chain(() -> Uni.createFrom().failure(new AuthenticationRedirectException(sessionExpiredUri)));
.chain(() -> Uni.createFrom().failure(new AuthenticationRedirectException(
filterRedirect(context, configContext, sessionExpiredUri))));
}

private static String decryptIdTokenIfEncryptedByProvider(TenantConfigContext resolvedContext, String token) {
Expand Down Expand Up @@ -692,6 +705,7 @@ && isRedirectFromProvider(context, configContext)) {
String authorizationURL = configContext.provider.getMetadata().getAuthorizationUri() + "?"
+ codeFlowParams.toString();

authorizationURL = filterRedirect(context, configContext, authorizationURL);
LOG.debugf("Code flow redirect to: %s", authorizationURL);

return Uni.createFrom().item(new ChallengeData(HttpResponseStatus.FOUND.code(), HttpHeaders.LOCATION,
Expand Down Expand Up @@ -848,7 +862,8 @@ public SecurityIdentity apply(SecurityIdentity identity) {
String finalRedirectUri = finalUriWithoutQuery.toString();
LOG.debugf("Removing code flow redirect parameters, final redirect URI: %s",
finalRedirectUri);
throw new AuthenticationRedirectException(finalRedirectUri);
throw new AuthenticationRedirectException(
filterRedirect(context, configContext, finalRedirectUri));
} else {
return identity;
}
Expand Down Expand Up @@ -1151,18 +1166,9 @@ static ServerCookie createCookie(RoutingContext context, OidcTenantConfig oidcCo

static ServerCookie createCookie(RoutingContext context, OidcTenantConfig oidcConfig,
String name, String value, long maxAge, boolean sessionCookie) {
ServerCookie cookie = new CookieImpl(name, value);
cookie.setHttpOnly(true);
cookie.setSecure(oidcConfig.authentication.cookieForceSecure || context.request().isSSL());
cookie.setMaxAge(maxAge);
LOG.debugf(name + " cookie 'max-age' parameter is set to %d", maxAge);
Authentication auth = oidcConfig.getAuthentication();
OidcUtils.setCookiePath(context, auth, cookie);
if (auth.cookieDomain.isPresent()) {
cookie.setDomain(auth.getCookieDomain().get());
}
ServerCookie cookie = OidcUtils.createCookie(context, oidcConfig, name, value, maxAge);
if (sessionCookie) {
cookie.setSameSite(CookieSameSite.valueOf(auth.cookieSameSite.name()));
cookie.setSameSite(CookieSameSite.valueOf(oidcConfig.authentication.cookieSameSite.name()));
}
context.response().addCookie(cookie);
return cookie;
Expand Down Expand Up @@ -1369,7 +1375,7 @@ private Uni<Void> buildLogoutRedirectUriUni(RoutingContext context, TenantConfig
public Void apply(Void t) {
String logoutUri = buildLogoutRedirectUri(configContext, idToken, context);
LOG.debugf("Logout uri: %s", logoutUri);
throw new AuthenticationRedirectException(logoutUri);
throw new AuthenticationRedirectException(filterRedirect(context, configContext, logoutUri));
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.impl.CookieImpl;
import io.vertx.core.http.impl.ServerCookie;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
Expand Down Expand Up @@ -491,7 +492,7 @@ static Uni<Void> removeSessionCookie(RoutingContext context, OidcTenantConfig oi
}
}

static String removeCookie(RoutingContext context, OidcTenantConfig oidcConfig, String cookieName) {
public static String removeCookie(RoutingContext context, OidcTenantConfig oidcConfig, String cookieName) {
ServerCookie cookie = (ServerCookie) context.cookieMap().get(cookieName);
String cookieValue = null;
if (cookie != null) {
Expand Down Expand Up @@ -786,4 +787,20 @@ public static boolean cacheUserInfoInIdToken(DefaultTenantConfigResolver resolve
return resolver.getTokenStateManager() instanceof DefaultTokenStateManager
&& oidcConfig.tokenStateManager.encryptionRequired;
}

public static ServerCookie createCookie(RoutingContext context, OidcTenantConfig oidcConfig,
String name, String value, long maxAge) {
ServerCookie cookie = new CookieImpl(name, value);
cookie.setHttpOnly(true);
cookie.setSecure(oidcConfig.authentication.cookieForceSecure || context.request().isSSL());
cookie.setMaxAge(maxAge);
LOG.debugf(name + " cookie 'max-age' parameter is set to %d", maxAge);
Authentication auth = oidcConfig.getAuthentication();
OidcUtils.setCookiePath(context, oidcConfig.getAuthentication(), cookie);
if (auth.cookieDomain.isPresent()) {
cookie.setDomain(auth.getCookieDomain().get());
}
context.response().addCookie(cookie);
return cookie;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.oidc.runtime;

import java.nio.charset.StandardCharsets;
import java.util.List;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
Expand All @@ -10,6 +11,7 @@

import io.quarkus.oidc.OIDCException;
import io.quarkus.oidc.OidcConfigurationMetadata;
import io.quarkus.oidc.OidcRedirectFilter;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.common.runtime.OidcCommonUtils;
import io.quarkus.runtime.configuration.ConfigurationException;
Expand All @@ -27,6 +29,8 @@ public class TenantConfigContext {
*/
final OidcTenantConfig oidcConfig;

final List<OidcRedirectFilter> redirectFilters;

/**
* PKCE Secret Key
*/
Expand All @@ -46,6 +50,7 @@ public TenantConfigContext(OidcProvider client, OidcTenantConfig config) {
public TenantConfigContext(OidcProvider client, OidcTenantConfig config, boolean ready) {
this.provider = client;
this.oidcConfig = config;
this.redirectFilters = TenantFeatureFinder.find(config, OidcRedirectFilter.class);
this.ready = ready;

boolean isService = OidcUtils.isServiceApp(config);
Expand Down Expand Up @@ -159,6 +164,10 @@ public OidcTenantConfig getOidcTenantConfig() {
return oidcConfig;
}

public List<OidcRedirectFilter> getOidcRedirectFilters() {
return redirectFilters;
}

public OidcConfigurationMetadata getOidcMetadata() {
return provider != null ? provider.getMetadata() : null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.quarkus.it.keycloak;

import jakarta.enterprise.context.ApplicationScoped;

import org.eclipse.microprofile.jwt.Claims;

import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.AuthorizationCodeTokens;
import io.quarkus.oidc.OidcRedirectFilter;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.TenantFeature;
import io.quarkus.oidc.runtime.OidcUtils;
import io.smallrye.jwt.build.Jwt;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
@Unremovable
@TenantFeature("tenant-refresh")
public class CustomOidcRedirectFilter implements OidcRedirectFilter {

@Override
public String filter(RoutingContext routingContext, OidcTenantConfig oidcConfig, String redirectUri) {
if (!"tenant-refresh".equals(oidcConfig.tenantId.get())) {
throw new RuntimeException("Invalid tenant id");
}
if (redirectUri.endsWith("/session-expired-page")) {

AuthorizationCodeTokens tokens = routingContext.get(AuthorizationCodeTokens.class.getName());
String userName = OidcUtils.decodeJwtContent(tokens.getIdToken()).getString(Claims.preferred_username.name());
String jwe = Jwt.preferredUserName(userName).jwe().encryptWithSecret(oidcConfig.credentials.secret.get());
OidcUtils.createCookie(routingContext, oidcConfig, "session_expired",
jwe + "|" + oidcConfig.tenantId.get(), 10);
return redirectUri + "?session-expired=true";
}
return redirectUri;
}

}
Loading

0 comments on commit bd52f6f

Please sign in to comment.