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

Updating selected OIDC/OpenID guides #42846

Merged
merged 3 commits into from
Aug 29, 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
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 @@
----
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 @@

[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.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.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 @@
[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 @@
[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 @@
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 @@ -780,13 +790,13 @@
==== State cookies

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.

Check warning on line 793 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": 793, "column": 1}}}, "severity": "INFO"}
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.

Check warning on line 796 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.Spelling] Use correct American English spelling. Did you really mean 'mins'? Raw Output: {"message": "[Quarkus.Spelling] Use correct American English spelling. Did you really mean 'mins'?", "location": {"path": "docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc", "range": {"start": {"line": 796, "column": 35}}}, "severity": "WARNING"}

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.

Check warning on line 798 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 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc", "range": {"start": {"line": 798, "column": 200}}}, "severity": "WARNING"}

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

@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 @@

@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 @@
@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 @@
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 @@

[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 @@

[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 @@
@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`.

Check warning on line 1464 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": 1464, "column": 220}}}, "severity": "INFO"}

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 @@
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 @@
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 @@

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 @@

page = form.getInputByValue("login").click();

assertEquals("alice", page.getBody().asText());
assertEquals("alice", page.getBody().asNormalizedText());
}
}

Expand Down Expand Up @@ -1901,7 +1912,7 @@
[[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
Loading