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

Introduce OidcResponseFilter #43283

Merged
merged 1 commit into from
Sep 19, 2024
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
54 changes: 52 additions & 2 deletions docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@
public void filter(OidcRequestContext requestContext) {
OidcConfigurationMetadata metadata = requestContext.contextProperties().get(OidcConfigurationMetadata.class.getName()); <1>
// Metadata URI is absolute, request URI value is relative
if (metadata.getTokenUri().endsWith(request.uri())) { <2>
if (metadata.getTokenUri().endsWith(requestContext.request().uri())) { <2>
requestContext.request().putHeader("TokenGrantDigest", calculateDigest(requestContext.requestBody().toString()));
}
}
Expand All @@ -348,7 +348,7 @@
<1> Get `OidcConfigurationMetadata`, which contains all supported OIDC endpoint addresses.
<2> Use `OidcConfigurationMetadata` to filter requests to the OIDC token endpoint only.

Alternatively, you can use an `@OidcEndpoint` annotation to apply this filter to the token endpoint requests only:
Alternatively, you can use an `@OidcEndpoint` annotation to apply this filter to responses from the OIDC discovery endpoint only:

[source,java]
----
Expand All @@ -375,6 +375,56 @@
----
<1> Restrict this filter to requests targeting the OIDC discovery endpoint only.

`OidcRequestContextProperties` can be used to access request properties.
Currently, you can use a `tenand_id` key to access the OIDC tenant id and a `grant_type` key to access the grant type which the OIDC provider uses to acquire tokens.

Check warning on line 379 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": 379, "column": 118}}}, "severity": "INFO"}
The `grant_type` can only be set to either `authorization_code` or `refresh_token` grant type, when requests are made to the token endpoint. It is `null` in all other cases.

[[code-flow-oidc-response-filters]]
=== OIDC response filters

You can filter responses from the OIDC providers by registering one or more `OidcResponseFilter` implementations, which can check the response status, headers and body in order to log them or perform other actions.

Check warning on line 385 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.TermsWarnings] Consider using 'to' rather than 'in order to' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'to' rather than 'in order to' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc", "range": {"start": {"line": 385, "column": 169}}}, "severity": "WARNING"}

You can have a single filter intercepting all the OIDC responses, or use an `@OidcEndpoint` annotation to apply this filter to the specific endpoint responses only. For example:

[source,java]
----
package io.quarkus.it.keycloak;

import jakarta.enterprise.context.ApplicationScoped;

import org.jboss.logging.Logger;

import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.common.OidcEndpoint;
import io.quarkus.oidc.common.OidcEndpoint.Type;
import io.quarkus.oidc.common.OidcResponseFilter;
import io.quarkus.oidc.common.runtime.OidcConstants;
import io.quarkus.oidc.runtime.OidcUtils;

@ApplicationScoped
@Unremovable
@OidcEndpoint(value = Type.TOKEN) <1>
public class TokenEndpointResponseFilter implements OidcResponseFilter {
private static final Logger LOG = Logger.getLogger(TokenResponseFilter.class);

@Override
public void filter(OidcResponseContext rc) {
String contentType = rc.responseHeaders().get("Content-Type"); <2>
if (contentType.equals("application/json")
&& OidcConstants.AUTHORIZATION_CODE.equals(rc.requestProperties().get(OidcConstants.GRANT_TYPE)) <3>
&& "code-flow-user-info-cached-in-idtoken".equals(rc.requestProperties().get(OidcUtils.TENANT_ID_ATTRIBUTE)) <3>
&& rc.responseBody().toJsonObject().containsKey("id_token")) { <4>
LOG.debug("Authorization code completed for tenant 'code-flow-user-info-cached-in-idtoken'");
}
}
}

----
<1> Restrict this filter to requests targeting the OIDC token endpoint only.
<2> Check the response `Content-Type` header.
<3> Use `OidcRequestContextProperties` request properties to check only an `authorization_code` token grant response for the `code-flow-user-info-cached-in-idtoken` tenant.
<4> Confirm the response JSON contains an `id_token` property.

=== Redirecting to and from the OIDC provider

When a user is redirected to the OIDC provider to authenticate, the redirect URL includes a `redirect_uri` query parameter, which indicates to the provider where the user has to be redirected to when the authentication is complete.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1107,7 +1107,9 @@ quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientRecorder".min-lev
[[oidc-client-ref-oidc-request-filters]]
== OIDC request filters

You can filter OIDC requests made by Quarkus to the OIDC provider by registering one or more `OidcRequestFilter` implementations, which can update or add new request headers. For example, a filter can analyze the request body and add its digest as a new header value:
You can filter OIDC requests made by OIDC client to the OIDC provider by registering one or more `OidcRequestFilter` implementations, which can update or add new request headers, or analyze the request body.

You can have a single filter intercepting requests to all OIDC provider endpoints, or use an `@OidcEndpoint` annotation to apply this filter to requests to specific endpoints only. For example:

[source,java]
----
Expand All @@ -1116,19 +1118,20 @@ package io.quarkus.it.keycloak;
import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.common.OidcRequestContext;
import io.quarkus.oidc.common.OidcEndpoint;
import io.quarkus.oidc.common.OidcRequestFilter;
import io.vertx.core.http.HttpMethod;

@ApplicationScoped
@OidcEndpoint(value = Type.TOKEN)
@Unremovable
public class OidcRequestCustomizer implements OidcRequestFilter {

@Override
public void filter(OidcRequestContext requestContext) {
HttpMethod method = requestContext.request().method();
String uri = requestContext.request().uri();
if (method == HttpMethod.POST && uri.endsWith("/service") && requestContext.requestBody() != null) {
if (method == HttpMethod.POST && uri.endsWith("/token") && requestContext.requestBody() != null) {
requestContext.request().putHeader("Digest", calculateDigest(requestContext.requestBody().toString()));
}
}
Expand All @@ -1139,6 +1142,53 @@ public class OidcRequestCustomizer implements OidcRequestFilter {
}
----

`OidcRequestContextProperties` can be used to access request properties.
Currently, you can use a `client_id` key to access the client tenant id and a `grant_type` key to access the grant type which the OIDC client uses to acquire tokens.

[[oidc-client-ref-oidc-response-filters]]
== OIDC response filters

You can filter responses to the OIDC client requests by registering one or more `OidcResponseFilter` implementations, which can check the response status, headers and body, in order to log them or perform other actions.

You can have a single filter intercepting responses to all OIDC client requests, or use an `@OidcEndpoint` annotation to apply this filter to the responses to the specific OIDC client requests only. For example:

[source,java]
----
package io.quarkus.it.keycloak;

import jakarta.enterprise.context.ApplicationScoped;

import org.jboss.logging.Logger;

import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.common.OidcEndpoint;
import io.quarkus.oidc.common.OidcEndpoint.Type;
import io.quarkus.oidc.common.OidcResponseFilter;
import io.quarkus.oidc.common.runtime.OidcConstants;

@ApplicationScoped
@Unremovable
@OidcEndpoint(value = Type.TOKEN) <1>
public class TokenEndpointResponseFilter implements OidcResponseFilter {
private static final Logger LOG = Logger.getLogger(TokenEndpointResponseFilter.class);

@Override
public void filter(OidcResponseContext rc) {
String contentType = rc.responseHeaders().get("Content-Type"); <2>
if (contentType.equals("application/json")
&& "refresh_token".equals(rc.requestProperties().get(OidcConstants.GRANT_TYPE)) <3>
&& rc.responseBody().toJsonObject().containsKey("refresh_token")) { <4>
LOG.debug("Tokens have been refreshed");
}
}

}
----
<1> Restrict this filter to requests targeting the OIDC token endpoint only.
<2> Check the response `Content-Type` header.
<3> Use `OidcRequestContextProperties` request properties to confirm it is a `refresh_grant` token grant response.
<4> Confirm the response JSON contains a `refresh_token` property.

[[token-propagation-rest]]
== Token Propagation for Quarkus REST

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -493,9 +493,132 @@
}
----

If you register clients dynamically, on demand, as described in the <<register-clients-on-demand>> section, the problem of the duplicate client registration should not arise.

Check warning on line 496 in docs/src/main/asciidoc/security-openid-connect-client-registration.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-openid-connect-client-registration.adoc", "range": {"start": {"line": 496, "column": 49}}}, "severity": "INFO"}
You can persist the already registered client's registration URI and registration token if necessary though and check them too to avoid any duplicate reservation risk.

[[oidc-client-registration-oidc-request-filters]]
== OIDC request filters

You can filter OIDC client registration and registered client requests registering one or more `OidcRequestFilter` implementations, which can update or add new request headers. For example, a filter can analyze the request body and add its digest as a new header value:

You can have a single filter intercepting all the OIDC registration and registered client requests, or use an `@OidcEndpoint` annotation to apply this filter to either OIDC registration or registered client endpoint responses only. For example:

[source,java]
----
package io.quarkus.it.keycloak;

import org.jboss.logging.Logger;

import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.common.OidcEndpoint;
import io.quarkus.oidc.common.OidcEndpoint.Type;
import io.quarkus.oidc.common.OidcRequestFilter;
import io.vertx.core.json.JsonObject;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
@Unremovable
@OidcEndpoint(value = Type.CLIENT_REGISTRATION) <1>
public class ClientRegistrationRequestFilter implements OidcRequestFilter {
private static final Logger LOG = Logger.getLogger(ClientRegistrationRequestFilter.class);

@Override
public void filter(OidcRequestContext rc) {
JsonObject body = rc.requestBody().toJsonObject();
if ("Default Client".equals(body.getString("client_name"))) { <2>
LOG.debug("'Default Client' registration request");
}
}

}
----
<1> Restrict this filter to requests targeting the OIDC client registration endpoint only.
<2> Check the 'client_name' property in the request metadata JSON.

Check warning on line 536 in docs/src/main/asciidoc/security-openid-connect-client-registration.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Spelling] Use correct American English spelling. Did you really mean 'client_name'? Raw Output: {"message": "[Quarkus.Spelling] Use correct American English spelling. Did you really mean 'client_name'?", "location": {"path": "docs/src/main/asciidoc/security-openid-connect-client-registration.adoc", "range": {"start": {"line": 536, "column": 16}}}, "severity": "WARNING"}

`OidcRequestContextProperties` can be used to access request properties.
Currently, you can use a `client_id` key to access the client tenant id and a `grant_type` key to access the grant type which the OIDC client uses to acquire tokens.

Check warning on line 539 in docs/src/main/asciidoc/security-openid-connect-client-registration.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-openid-connect-client-registration.adoc", "range": {"start": {"line": 539, "column": 120}}}, "severity": "INFO"}

[[oidc-client-registration-oidc-response-filters]]
== OIDC response filters

You can filter responses to OIDC client registration and registered client requests by registering one or more `OidcResponseFilter` implementations, which can check the response status, headers and body in order to log them or perform other actions.

Check warning on line 544 in docs/src/main/asciidoc/security-openid-connect-client-registration.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-openid-connect-client-registration.adoc", "range": {"start": {"line": 544, "column": 1}}}, "severity": "INFO"}

Check warning on line 544 in docs/src/main/asciidoc/security-openid-connect-client-registration.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Be concise: use 'to' rather than' rather than 'in order to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Be concise: use 'to' rather than' rather than 'in order to'.", "location": {"path": "docs/src/main/asciidoc/security-openid-connect-client-registration.adoc", "range": {"start": {"line": 544, "column": 204}}}, "severity": "INFO"}

Check warning on line 544 in docs/src/main/asciidoc/security-openid-connect-client-registration.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'to' rather than 'in order to' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'to' rather than 'in order to' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/security-openid-connect-client-registration.adoc", "range": {"start": {"line": 544, "column": 204}}}, "severity": "WARNING"}

You can have a single filter intercepting responses to all the OIDC registration and registered client requests, or use an `@OidcEndpoint` annotation to apply this filter to responses from either OIDC registration or registered client endpoint only. For example:

Check warning on line 546 in docs/src/main/asciidoc/security-openid-connect-client-registration.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-openid-connect-client-registration.adoc", "range": {"start": {"line": 546, "column": 1}}}, "severity": "INFO"}

[source,java]
----
package io.quarkus.it.keycloak;

import jakarta.enterprise.context.ApplicationScoped;

import org.jboss.logging.Logger;

import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.common.OidcEndpoint;
import io.quarkus.oidc.common.OidcEndpoint.Type;
import io.quarkus.oidc.common.OidcResponseFilter;
import io.vertx.core.json.JsonObject;

@ApplicationScoped
@Unremovable
@OidcEndpoint(value = Type.CLIENT_REGISTRATION) <1>
public class ClientRegistrationResponseFilter implements OidcResponseFilter {
private static final Logger LOG = Logger.getLogger(ClientRegistrationResponseFilter.class);

@Override
public void filter(OidcResponseContext rc) {
String contentType = rc.responseHeaders().get("Content-Type"); <2>
JsonObject body = rc.responseBody().toJsonObject();
if (contentType.startsWith("application/json")
&& "Default Client".equals(body.getString("client_name"))) { <3>
LOG.debug("'Default Client' has been registered");
}
}

}

----
<1> Restrict this filter to requests targeting the OIDC client registration endpoint only.
<2> Check the response `Content-Type` header.
<3> Check the 'client_name' property in the response metadata JSON.

Check warning on line 583 in docs/src/main/asciidoc/security-openid-connect-client-registration.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Spelling] Use correct American English spelling. Did you really mean 'client_name'? Raw Output: {"message": "[Quarkus.Spelling] Use correct American English spelling. Did you really mean 'client_name'?", "location": {"path": "docs/src/main/asciidoc/security-openid-connect-client-registration.adoc", "range": {"start": {"line": 583, "column": 16}}}, "severity": "WARNING"}

or

[source,java]
----
package io.quarkus.it.keycloak;

import jakarta.enterprise.context.ApplicationScoped;

import org.jboss.logging.Logger;

import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.common.OidcEndpoint;
import io.quarkus.oidc.common.OidcEndpoint.Type;
import io.quarkus.oidc.common.OidcResponseFilter;

@ApplicationScoped
@Unremovable
@OidcEndpoint(value = Type.REGISTERED_CLIENT) <1>
public class RegisteredClientResponseFilter implements OidcResponseFilter {
private static final Logger LOG = Logger.getLogger(RegisteredClientResponseFilter.class);

@Override
public void filter(OidcResponseContext rc) {
String contentType = rc.responseHeaders().get("Content-Type"); <2>
if (contentType.startsWith("application/json")
&& "Default Client Updated".equals(rc.responseBody().toJsonObject().getString("client_name"))) { <3>
LOG.debug("Registered 'Default Client' has had its name updated to 'Default Client Updated'");
}
}

}

----
<1> Restrict this filter to requests targeting the registered OIDC client endpoint only.
<2> Check the response `Content-Type` header.
<3> Confirm the client name property was updated.

[[configuration-reference]]
== Configuration reference

Expand Down
Loading
Loading