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

Improve OIDC bearer token concept doc #37692

Merged
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@

To learn about how you can protect service applications by using OIDC Bearer token authentication, see xref:security-oidc-bearer-token-authentication-tutorial.adoc[OIDC Bearer token authentication tutorial].

If you want to protect web applications by using OIDC authorization code flow authentication, see xref:security-oidc-code-flow-authentication-concept.adoc[OIDC authorization code flow authentication].
If you want to protect web applications by using OIDC authorization code flow authentication, see xref:security-oidc-code-flow-authentication.adoc[OIDC authorization code flow authentication].

For information about how to support multiple tenants, see xref:security-openid-connect-multitenancy.adoc[Using OpenID Connect Multi-Tenancy].

Expand Down Expand Up @@ -108,6 +108,27 @@
* If `groups` claim is available then its value is used
* If `realm_access/roles` or `resource_access/client_id/roles` (where `client_id` is the value of the `quarkus.oidc.client-id` property) claim is available then its value is used.
This check supports the tokens issued by Keycloak

For example, the following JWT token has a complex `groups` claim which contains an array `roles` containing roles:

Check warning on line 112 in docs/src/main/asciidoc/security-oidc-bearer-token-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-bearer-token-authentication.adoc", "range": {"start": {"line": 112, "column": 66}}}, "severity": "INFO"}

[source,json]
----
{
"iss": "https://server.example.com",
"sub": "24400320",
"upn": "[email protected]",
"preferred_username": "jdoe",
"exp": 1311281970,
"iat": 1311280970,
"groups": {
"roles": [
"microprofile_jwt_user"
],
}
}
----

`microprofile_jwt_user` role has to be mapped to SecurityIdentity roles and you can do it with this configuration: `quarkus.oidc.roles.role-claim-path=groups/roles`.

If the token is opaque (binary) then a `scope` property from the remote token introspection response will be used.

Expand All @@ -123,6 +144,7 @@

[source, java]
----
import java.util.List;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
Expand Down Expand Up @@ -152,6 +174,20 @@
return List.of(new Order(1));
}

public static class Order {
String id;
public Order() {
}
public Order(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setId() {
this.id = id;
}
}
}
----
<1> Only requests with OpenID Connect scope `email` are going to be granted access.
Expand Down Expand Up @@ -262,6 +298,7 @@
import jakarta.inject.Inject;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.Provider;

import org.eclipse.microprofile.jwt.JsonWebToken;
Expand Down Expand Up @@ -558,7 +595,7 @@

@AfterAll
public static void close() {
client.close();
oidcTestClient.close();
}

@Test
Expand Down Expand Up @@ -739,7 +776,6 @@
import static io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager.getAccessToken;
import static org.hamcrest.Matchers.equalTo;

import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;

import io.quarkus.test.common.QuarkusTestResource;
Expand All @@ -753,7 +789,7 @@

@Test
public void testBearerToken() {
RestAssured.given().auth().oauth2(getAccessToken("alice"))))
RestAssured.given().auth().oauth2(getAccessToken("alice"))
.when().get("/api/users/preferredUserName")
.then()
.statusCode(200)
Expand Down Expand Up @@ -782,7 +818,7 @@
smallrye.jwt.sign.key.location=/privateKey.pem
----

copy `privateKey.pem` from the `integration-tests/oidc-tenancy` in the `main` Quarkus repository and use a test code similar to the one in the `Wiremock` section above to generate JWT tokens. You can use your own test keys if preferred.
copy link:{quarkus-blob-url}/integration-tests/oidc-tenancy/src/main/resources/privateKey.pem[privateKey.pem] from the `integration-tests/oidc-tenancy` in the `main` Quarkus repository and use a test code similar to the one in the `Wiremock` section above to generate JWT tokens. You can use your own test keys if preferred.

This approach provides a more limited coverage compared to the Wiremock approach - for example, the remote communication code is not covered.

Expand Down Expand Up @@ -857,8 +893,14 @@

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

import io.quarkus.oidc.OidcConfigurationMetadata;
import io.quarkus.oidc.UserInfo;
import io.quarkus.security.Authenticated;

import org.eclipse.microprofile.jwt.JsonWebToken;

@Path("/service")
Expand Down Expand Up @@ -929,7 +971,12 @@

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

import io.quarkus.oidc.TokenIntrospection;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;

@Path("/service")
Expand Down Expand Up @@ -1040,6 +1087,7 @@
import static jakarta.ws.rs.core.HttpHeaders.AUTHORIZATION;

import jakarta.inject.Inject;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import io.vertx.core.eventbus.EventBus;
Expand All @@ -1053,7 +1101,18 @@
@POST
public void order(String product, @HeaderParam(AUTHORIZATION) String bearer) {
String rawToken = bearer.substring("Bearer ".length()); <1>
eventBus.publish("product-order", new Product(product, 1, rawToken));
eventBus.publish("product-order", new Product(product, rawToken));
}

public static class Product {
public String product;
public String customerAccessToken;
public Product() {
}
public Product(String product, String customerAccessToken) {
this.product = product;
this.customerAccessToken = customerAccessToken;
}
}
}
----
Expand Down Expand Up @@ -1086,9 +1145,8 @@
@Blocking
@ConsumeEvent("product-order")
void processOrder(Product product) {
String rawToken = product.customerAccessToken;
AccessTokenCredential token = new AccessTokenCredential(rawToken);
SecurityIdentity = identityProvider.authenticate(token).await().indefinitely(); <2>
AccessTokenCredential tokenCredential = new AccessTokenCredential(product.customerAccessToken);
SecurityIdentity securityIdentity = identityProvider.authenticate(tokenCredential).await().indefinitely(); <2>
...
}

Expand Down Expand Up @@ -1120,7 +1178,6 @@

* xref:security-oidc-configuration-properties-reference.adoc[OIDC configuration properties]
* xref:security-oidc-bearer-token-authentication-tutorial.adoc[Protect a service application by using OIDC Bearer token authentication]
* xref:security-protect-service-applications-by-using-oidc-bearer-authentication-how-to.adoc[Protect service applications by using OIDC Bearer token authentication]
* https://www.keycloak.org/documentation.html[Keycloak Documentation]
* https://openid.net/connect/[OpenID Connect]
* https://tools.ietf.org/html/rfc7519[JSON Web Token]
Expand Down