Skip to content

Commit

Permalink
Improve OIDC bearer token concept doc
Browse files Browse the repository at this point in the history
  • Loading branch information
sberyozkin committed Dec 12, 2023
1 parent af6d3d7 commit 39b509a
Showing 1 changed file with 65 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,27 @@ SecurityIdentity roles can be mapped from the verified JWT access tokens as foll
* 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 @@ SecurityIdentity permissions are mapped in the form of the `io.quarkus.security.

[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 @@ public class ProtectedResource {
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 @@ In such cases you may want to consider skipping the issuer verification by setti
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 @@ -739,7 +776,6 @@ and finally write the test code, for example:
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 @@ public class BearerTokenAuthorizationTest {
@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 @@ quarkus.oidc.public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y
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:https://github.com/quarkusio/quarkus/tree/main/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 @@ where `ProtectedResource` class may look like this:

[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 @@ where `ProtectedResource` class may look like this:

[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 @@ package org.acme.quickstart.oidc;
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 @@ public class OrderResource {
@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 @@ public class OrderService {
@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 @@ For more information, see xref:security-code-flow-authentication#oidc-request-fi

* 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

0 comments on commit 39b509a

Please sign in to comment.