Skip to content

Commit

Permalink
Merge pull request #42846 from jedla97/oicd-docs-small-fixes
Browse files Browse the repository at this point in the history
Updating selected OIDC/OpenID guides
  • Loading branch information
sberyozkin committed Aug 29, 2024
2 parents c96ef2a + b655d52 commit d476da6
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ If you need to authenticate and authorize users by using OIDC authorization code
Also, if you use Keycloak and bearer tokens, see the Quarkus xref:security-keycloak-authorization.adoc[Using Keycloak to centralize authorization] guide.

To learn about how you can protect service applications by using OIDC Bearer token authentication, see the following tutorial:

* xref:security-oidc-bearer-token-authentication-tutorial.adoc[Protect a web application by using OpenID Connect (OIDC) authorization code flow].

For information about how to support multiple tenants, see the Quarkus xref:security-openid-connect-multitenancy.adoc[Using OpenID Connect Multi-Tenancy] guide.
Expand Down Expand Up @@ -183,7 +184,7 @@ public class ProtectedResource {
@GET
@Path("/order")
public List<Order> listOrders() {
return List.of(new Order(1));
return List.of(new Order("1"));
}
public static class Order {
Expand Down Expand Up @@ -1018,7 +1019,6 @@ import io.quarkus.test.security.TestSecurity;
import io.quarkus.test.security.oidc.Claim;
import io.quarkus.test.security.oidc.ConfigMetadata;
import io.quarkus.test.security.oidc.OidcSecurity;
import io.quarkus.test.security.oidc.OidcConfigurationMetadata;
import io.quarkus.test.security.oidc.UserInfo;
import io.restassured.RestAssured;
Expand Down Expand Up @@ -1122,8 +1122,8 @@ public class TestSecurityAuthTest {
}
)
public void testOidcWithClaimsUserInfoAndMetadata() {
RestAssured.when().get("test-security-oidc-claims-userinfo-metadata").then()
.body(is("userOidc:viewer:userOidc:viewer"));
RestAssured.when().get("test-security-oidc-opaque-token").then()
.body(is("userOidc:viewer:userOidc:viewer:user@gmail.com"));
}
}
Expand Down Expand Up @@ -1315,7 +1315,7 @@ public class OrderService {
@Blocking
@ConsumeEvent("product-order")
void processOrder(Product product) {
void processOrder(OrderResource.Product product) {
AccessTokenCredential tokenCredential = new AccessTokenCredential(product.customerAccessToken);
SecurityIdentity securityIdentity = identityProvider.authenticate(tokenCredential).await().indefinitely(); <2>
...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ The OIDC extension allows you to define the configuration by using the `applicat

[source,properties]
----
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
%prod.quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=frontend
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app
Expand Down
57 changes: 34 additions & 23 deletions docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ For example:
----
package io.quarkus.it.keycloak;
import io.quarkus.oidc.OidcConfigurationMetadata;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.arc.Unremovable;
Expand Down Expand Up @@ -350,6 +351,8 @@ Alternatively, you can use `OidcRequestFilter.Endpoint` enum to apply this filte

[source,java]
----
package io.quarkus.it.keycloak;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.arc.Unremovable;
Expand Down Expand Up @@ -480,6 +483,7 @@ import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.AuthorizationCodeTokens;
import io.quarkus.oidc.OidcRedirectFilter;
import io.quarkus.oidc.Redirect;
import io.quarkus.oidc.Redirect.Location;
import io.quarkus.oidc.TenantFeature;
import io.quarkus.oidc.runtime.OidcUtils;
import io.smallrye.jwt.build.Jwt;
Expand Down Expand Up @@ -526,12 +530,15 @@ import org.eclipse.microprofile.jwt.Claims;
import org.eclipse.microprofile.jwt.JsonWebToken;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.runtime.OidcUtils;
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
RoutingContext context;
@Inject
TenantConfigBean tenantConfig; <1>
Expand Down Expand Up @@ -572,6 +579,8 @@ You can access ID token claims by injecting `JsonWebToken` with an `IdToken` qua
[source, java]
----
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.jwt.JsonWebToken;
import io.quarkus.oidc.IdToken;
import io.quarkus.security.Authenticated;
Expand All @@ -597,6 +606,8 @@ You can access the raw access token as follows:
[source, java]
----
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.jwt.JsonWebToken;
import io.quarkus.oidc.AccessTokenCredential;
import io.quarkus.security.Authenticated;
Expand Down Expand Up @@ -765,8 +776,7 @@ For example:
By default, `quarkus.oidc.authentication.cookie-path` is set to `/` but you can change this to a more specific path if required, for example, `/web-app`.

To set the cookie path dynamically, configure the `quarkus.oidc.authentication.cookie-path-header` property.
Set the `quarkus.oidc.authentication.cookie-path-header` property.
For example, to set the cookie path dynamically by using the value of the`X-Forwarded-Prefix` HTTP header, configure the property to `quarkus.oidc.authentication.cookie-path-header=X-Forwarded-Prefix`.
For example, to set the cookie path dynamically by using the value of the `X-Forwarded-Prefix` HTTP header, configure the property to `quarkus.oidc.authentication.cookie-path-header=X-Forwarded-Prefix`.

If `quarkus.oidc.authentication.cookie-path-header` is set but no configured HTTP header is available in the current request, then the `quarkus.oidc.authentication.cookie-path` will be checked.

Expand All @@ -783,10 +793,10 @@ State cookies are used to support authorization code flow completion.
When an authorization code flow is started, Quarkus creates a state cookie and a matching `state` query parameter, before redirecting the user to the OIDC provider.
When the user is redirected back to Quarkus to complete the authorization code flow, Quarkus expects that the request URI must contain the `state` query parameter and it must match the current state cookie value.

The default state cookie age is 5 mins and you can change it with a `quarkus.oidc.authenticaion.state-cookie-age` Duration property.
The default state cookie age is 5 mins and you can change it with a `quarkus.oidc.authentication.state-cookie-age` Duration property.

Quarkus creates a unique state cookie name every time a new authorization code flow is started to support multi-tab authentication. Many concurrent authentication requests on behalf of the same user may cause a lot of state cookies be created.
If you do not want to allow your users use multiple browser tabs to authenticate then it is recommended to disable it with `quarkus.oidc.authenticaion.allow-multiple-code-flows=false`. It also ensures that the same state cookie name is created for every new user authentication.
If you do not want to allow your users use multiple browser tabs to authenticate then it is recommended to disable it with `quarkus.oidc.authentication.allow-multiple-code-flows=false`. It also ensures that the same state cookie name is created for every new user authentication.

[[token-state-manager]]
==== Session cookie and default TokenStateManager
Expand Down Expand Up @@ -891,14 +901,14 @@ public class CustomTokenStateManager implements TokenStateManager {
@Override
public Uni<String> createTokenState(RoutingContext routingContext, OidcTenantConfig oidcConfig,
AuthorizationCodeTokens sessionContent, TokenStateManager.CreateTokenStateRequestContext requestContext) {
AuthorizationCodeTokens sessionContent, OidcRequestContext<String> requestContext) {
return tokenStateManager.createTokenState(routingContext, oidcConfig, sessionContent, requestContext)
.map(t -> (t + "|custom"));
}
@Override
public Uni<AuthorizationCodeTokens> getTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig,
String tokenState, TokenStateManager.GetTokensRequestContext requestContext) {
String tokenState, OidcRequestContext<AuthorizationCodeTokens> requestContext) {
if (!tokenState.endsWith("|custom")) {
throw new IllegalStateException();
}
Expand All @@ -908,7 +918,7 @@ public class CustomTokenStateManager implements TokenStateManager {
@Override
public Uni<Void> deleteTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState,
TokenStateManager.DeleteTokensRequestContext requestContext) {
OidcRequestContext<Void> requestContext) {
if (!tokenState.endsWith("|custom")) {
throw new IllegalStateException();
}
Expand Down Expand Up @@ -1184,8 +1194,9 @@ public class ServiceResource {
@Path("logout")
public String logout() {
oidcSession.logout().await().indefinitely();
return "You are logged out".
return "You are logged out";
}
}
----

[[oidc-session]]
Expand Down Expand Up @@ -1287,10 +1298,8 @@ To support the integration with such OAuth2 servers, `quarkus-oidc` needs to be
Even though you configure the extension to support the authorization code flows without `IdToken`, an internal `IdToken` is generated to standardize the way `quarkus-oidc` operates.
You use an internal `IdToken` to support the authentication session and to avoid redirecting the user to the provider, such as GitHub, on every request.
In this case, the `IdToken` age is set to the value of a standard `expires_in` property in the authorization code flow response.
You can use a `quarkus.oidc.authentication.internal-id-token-lifespan`property to customize the ID token age.
The default ID token age is 5 minutes.
, which you can extend further as described in the <<session-management,session management>> section.
You can use a `quarkus.oidc.authentication.internal-id-token-lifespan` property to customize the ID token age.
The default ID token age is 5 minutes, which you can extend further as described in the <<session-management,session management>> section.
This simplifies how you handle an application that supports multiple OIDC providers.
====
Expand Down Expand Up @@ -1345,6 +1354,8 @@ This is all that is needed for an endpoint like this one to return the currently

[source,java]
----
package io.quarkus.it.keycloak;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
Expand Down Expand Up @@ -1417,6 +1428,8 @@ Now, the following code will work when the user signs into your application by u

[source,java]
----
package io.quarkus.it.keycloak;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
Expand All @@ -1435,23 +1448,23 @@ public class TokenResource {
@GET
@Path("/google")
@Produces("application/json")
public String getUserName() {
public String getGoogleUserName() {
return identity.getPrincipal().getName();
}
@GET
@Path("/github")
@Produces("application/json")
public String getUserName() {
return identity.getPrincipal().getUserName();
public String getGitHubUserName() {
return identity.getPrincipal().getName();
}
}
----

Possibly a simpler alternative is to inject both `@IdToken JsonWebToken` and `UserInfo` and use `JsonWebToken` when handling the providers that return `IdToken` and use `UserInfo` with the providers that do not return `IdToken`.

You must ensure that the callback path you enter in the GitHub OAuth application configuration matches the endpoint path where you want the user to be redirected after a successful GitHub authentication and application authorization.
In this case, it has to be set to `http:localhost:8080/github/userinfo`.
In this case, it has to be set to `http://localhost:8080/github/userinfo`.

[[listen-to-authentication-events]]
=== Listening to important authentication events
Expand All @@ -1466,9 +1479,7 @@ For example:
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import io.quarkus.oidc.IdTokenCredential;
import io.quarkus.oidc.SecurityEvent;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.vertx.ext.web.RoutingContext;
@ApplicationScoped
Expand Down Expand Up @@ -1640,8 +1651,8 @@ For example, name it as `OktaSaml`:
image::okta-saml-general-settings.png[alt=Okta SAML General Settings,role="center"]

Next, configure it to point to a Keycloak SAML broker endpoint.
At this point, you need to know the name of the Keycloak realm, for example, `quarkus`, and, assuming that the Keycloak SAML broker alias is `saml`, enter the endpoint address as `http:localhost:8081/realms/quarkus/broker/saml/endpoint`.
Enter the service provider (SP) entity ID as `http:localhost:8081/realms/quarkus`, where `http://localhost:8081` is a Keycloak base address and `saml` is a broker alias:
At this point, you need to know the name of the Keycloak realm, for example, `quarkus`, and, assuming that the Keycloak SAML broker alias is `saml`, enter the endpoint address as `http://localhost:8081/realms/quarkus/broker/saml/endpoint`.
Enter the service provider (SP) entity ID as `http://localhost:8081/realms/quarkus`, where `http://localhost:8081` is a Keycloak base address and `saml` is a broker alias:

image::okta-saml-configuration.png[alt=Okta SAML Configuration,role="center"]

Expand All @@ -1658,7 +1669,7 @@ Now, in the `quarkus` realm properties, navigate to `Identity Providers` and add

image::keycloak-add-saml-provider.png[alt=Keycloak Add SAML Provider,role="center"]

Note the alias is set to `saml`, `Redirect URI` is `http:localhost:8081/realms/quarkus/broker/saml/endpoint` and `Service provider entity ID` is `http:localhost:8081/realms/quarkus` - these are the same values you entered when creating the Okta SAML integration in the previous step.
Note the alias is set to `saml`, `Redirect URI` is `http://localhost:8081/realms/quarkus/broker/saml/endpoint` and `Service provider entity ID` is `http://localhost:8081/realms/quarkus` - these are the same values you entered when creating the Okta SAML integration in the previous step.

Finally, set `Service entity descriptor` to point to the Okta SAML Integration Metadata URL you noted at the end of the previous step.

Expand Down Expand Up @@ -1776,7 +1787,7 @@ public class CodeFlowAuthorizationTest {
page = form.getInputByValue("login").click();
assertEquals("alice", page.getBody().asText());
assertEquals("alice", page.getBody().asNormalizedText());
}
}
Expand Down Expand Up @@ -1901,7 +1912,7 @@ endif::no-deprecated-test-resource[]
[[code-flow-integration-testing-security-annotation]]
=== TestSecurity annotation

You can use @TestSecurity and @OidcSecurity annotations to test the `web-app` application endpoint code, which depends on either one of the following injections, or all four:
You can use `@TestSecurity` and `@OidcSecurity` annotations to test the `web-app` application endpoint code, which depends on either one of the following injections, or all four:

* ID `JsonWebToken`
* Access `JsonWebToken`
Expand Down
Loading

0 comments on commit d476da6

Please sign in to comment.