Skip to content

Commit

Permalink
Merge pull request #34659 from sberyozkin/well_known_providers_doc_up…
Browse files Browse the repository at this point in the history
…dates

Improve OIDC multi-tenancy and providers docs
  • Loading branch information
sberyozkin authored Jul 11, 2023
2 parents dea2d39 + e381873 commit 80826ec
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 4 deletions.
112 changes: 108 additions & 4 deletions docs/src/main/asciidoc/security-openid-connect-multitenancy.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,100 @@ You should be redirected again to the login page at Keycloak. However, now you a
In both cases, if the user is successfully authenticated, the landing page will show the user's name and e-mail. Even though
user `alice` exists in both tenants, for the application they are distinct users belonging to different realms/tenants.

== Resolving Tenant Identifiers with Annotations
[[static-tenant-resolution]]
== Static tenant configuration resolution

When you set multiple tenant configurations in the `application.properties` file, you only need to specify how the tenant identifier gets resolved.
To configure the resolution of the tenant identifier, use one of the following options:

* <<default-tenant-resolver>>
* <<tenant-resolver>>
* <<annotations-tenant-resolver>>

[[default-tenant-resolver]]
=== Default resolution

The default resolution for a tenant identifier is convention based, whereby the authentication request must include the tenant identifier in the last segment of the request path.

The following `application.properties` example shows how you can configure two tenants named `google` and `github`:

[source,properties]
----
# Tenant 'google' configuration
quarkus.oidc.google.provider=google
quarkus.oidc.google.client-id=${google-client-id}
quarkus.oidc.google.credentials.secret=${google-client-secret}
quarkus.oidc.google.authentication.redirect-path=/signed-in
# Tenant 'github' configuration
quarkus.oidc.github.provider=google
quarkus.oidc.github.client-id=${github-client-id}
quarkus.oidc.github.credentials.secret=${github-client-secret}
quarkus.oidc.github.authentication.redirect-path=/signed-in
----

In this example, both tenants configure OIDC `web-app` applications to use an authorization code flow to authenticate users and also require session cookies to get generated after the authentication has taken place.
After either Google or GitHub authenticates the current user, the user gets returned to the `/signed-in` area for authenticated users, for example, a secured resource path on the JAX-RS endpoint.

Finally, to complete the default tenant resolution, set the following configuration property:

[source,properties]
----
quarkus.http.auth.permission.login.paths=/google,/github
quarkus.http.auth.permission.login.policy=authenticated
----

If the endpoint is running on `http://localhost:8080`, you can also provide UI options for users to log in to either `http://localhost:8080/google` or `http://localhost:8080/github`, without having to add specific`/google` or `/github` JAX-RS resource paths.
Tenant identifiers are also recorded in the session cookie names after the authentication is completed.
Therefore, authenticated users can access the secured application area without requiring either the `google` or `github` path values to be included in the secured URL.

Default resolution can also work for Bearer token authentication but it might be less practical in this case because a tenant identifier will always need to be set as the last path segment value.

[[tenant-resolver]]
=== Resolve with `TenantResolver`

The following `application.properties` example shows how you can resolve the tenant identifier of two tenants named `a` and `b` by using the `TenantResolver` method:

[source,properties]
----
# Tenant 'a' configuration
quarkus.oidc.a.auth-server-url=http://localhost:8180/realms/quarkus-a
quarkus.oidc.a.client-id=client-a
quarkus.oidc.a.credentials.secret=client-a-secret
# Tenant 'b' configuration
quarkus.oidc.b.auth-server-url=http://localhost:8180/realms/quarkus-b
quarkus.oidc.b.client-id=client-b
quarkus.oidc.b.credentials.secret=client-b-secret
----

You can return the tenant ID of either `a` or `b` from `quarkus.oidc.TenantResolver`:

[source,java]
----
import quarkus.oidc.TenantResolver;
public class CustomTenantResolver implements TenantResolver {
@Override
public String resolve(RoutingContext context) {
String path = context.request().path();
if (path.endsWith("a")) {
return "a";
} else if (path.endsWith("b")) {
return "b";
} else {
// default tenant
return null;
}
}
}
----

In this example, the value of the last request path segment is a tenant ID, but if required, you can implement a more complex tenant identifier resolution logic.

[[annotations-tenant-resolver]]
=== Resolve with annotations

You can use the annotations and CDI interceptors for resolving the tenant identifiers as an alternative to using
`quarkus.oidc.TenantResolver`. This can be done by setting the value for the key `OidcUtils.TENANT_ID_ATTRIBUTE` on
Expand Down Expand Up @@ -624,7 +717,7 @@ Now all methods and classes carrying `@HrTenant` will be authenticated using the
OIDC provider.

[[tenant-config-resolver]]
== Programmatically Resolving Tenants Configuration
== Dynamic tenant configuration resolution

If you need a more dynamic configuration for the different tenants you want to support and don't want to end up with multiple
entries in your configuration file, you can use the `io.quarkus.oidc.TenantConfigResolver`.
Expand Down Expand Up @@ -688,7 +781,11 @@ public class CustomTenantConfigResolver implements TenantConfigResolver {

The `OidcTenantConfig` returned from this method is the same used to parse the `oidc` namespace configuration from the `application.properties`. You can populate it using any of the settings supported by the `quarkus-oidc` extension.

== Tenant Resolution for OIDC 'web-app' applications
=== Tenant resolution for OIDC `web-app` applications

The simplest option for resolving OIDC `web-app` application configuration is to follow the steps described in the <<default-tenant-resolver>> section.

Try one of the options suggested below if the default resolution strategy does not work for your application setup.

Several options are available for selecting the tenant configuration which should be used to secure the current HTTP request for both `service` and `web-app` OIDC applications, such as:

Expand Down Expand Up @@ -742,7 +839,14 @@ public class CustomTenantResolver implements TenantResolver {

Custom `TenantResolver` and `TenantConfigResolver` implementations may return `null` if no tenant can be inferred from the current request and a fallback to the default tenant configuration is required.

If it is expected that the custom resolvers will always infer a tenant then the default tenant configuration is not needed. One can disable it with the `quarkus.oidc.tenant-enabled=false` setting.
If you expect that the custom resolvers will always infer a tenant then you do not need to configure the default tenant resolution.

* To disable the default tenant configuration, set `quarkus.oidc.tenant-enabled=false`.

[NOTE]
====
The default tenant configuration is automatically disabled when `quarkus.oidc.auth-server-url` is not configured but either custom tenant configurations are available or `TenantConfigResolver` is registered.
====

Note that tenant specific configurations can also be disabled, for example: `quarkus.oidc.tenant-a.tenant-enabled=false`.

Expand Down
78 changes: 78 additions & 0 deletions docs/src/main/asciidoc/security-openid-connect-providers.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ This property can be used in `application.properties`, in xref:security-openid-c

== Well Known Providers

[[github]]
=== GitHub

In order to set up OIDC for GitHub you need to create a new OAuth application in your https://github.com/settings/applications/new[GitHub developer settings]:
Expand Down Expand Up @@ -51,6 +52,7 @@ quarkus.oidc.credentials.secret=<Secret>

TIP: You can also use GitHub provider with `quarkus.oidc.application-type=service`, just set `quarkus.oidc.verify-access-token-with-user-info` configuration property to `true`.

[[google]]
=== Google

In order to set up OIDC for Google you need to create a new project in your https://console.cloud.google.com/projectcreate[Google Cloud Platform console]:
Expand Down Expand Up @@ -101,6 +103,7 @@ quarkus.oidc.client-id=<Client ID>
quarkus.oidc.credentials.secret=<Secret>
----

[[microsoft]]
=== Microsoft

In order to set up OIDC for Microsoft you need to go to your https://portal.azure.com[Microsoft Azure Portal],
Expand Down Expand Up @@ -142,6 +145,7 @@ quarkus.oidc.client-id=<Client ID>
quarkus.oidc.credentials.secret=<Secret>
----

[[apple]]
=== Apple

In order to set up OIDC for Apple you need to create a developer account, and sign up for the 99€/year program, but you cannot test your application on `localhost` like most other OIDC providers:
Expand Down Expand Up @@ -248,6 +252,7 @@ quarkus.oidc.credentials.jwt.issuer=<App ID Prefix>
quarkus.oidc.credentials.jwt.subject=<Bundle ID}
----

[[facebook]]
=== Facebook

Facebook you will not be let you test your application on `localhost` like most other OIDC providers:
Expand Down Expand Up @@ -287,6 +292,7 @@ quarkus.oidc.client-id=<App ID>
quarkus.oidc.credentials.secret=<App secret>
----

[[twitter]]
=== Twitter

You can use Twitter for OIDC login, but at the moment, it restricts access to the user's email, which means you
Expand Down Expand Up @@ -353,6 +359,7 @@ You can provide your own secret key for encrypting the PKCE code verifier if you
note that this secret should be 32 characters long, and an error will be reported if it is less than 16 characters long.
====

[[spotify]]
=== Spotify

Create a https://developer.spotify.com/documentation/general/guides/authorization/app-settings/[Spotify application]:
Expand All @@ -372,6 +379,77 @@ quarkus.oidc.client-id=<Client ID>
quarkus.oidc.credentials.secret=<Client Secret>
----

== Support for multiple providers

If you would like to support authenticating users with more than one provider then a provider-specific tenant configuration resolution must be supported.

For more information, see the Quarkus xref:security-openid-connect-multitenancy.adoc#static-tenant-resolution[Using OpenID Connect (OIDC) Multi-Tenancy] guide.

== Access provider services with token propagation

Sometimes, only authenticating users with a social provider is not enough.
A provider-specific service also needs to be accessed for the Quarkus OIDC `web-app` application to fetch or update data from the provider service on behalf of the currently authenticated user.

As mentioned in the xref:security-oidc-code-flow-authentication.adoc[OIDC code flow mechanism for protecting web applications] guide, ID and access tokens are returned after the authorization code flow has been completed, with some providers like `GitHub` returning an access token only.
It is this access token that has to be propagated to services such as `Google Calendar`, or `Spotify Playlists` for the currently authenticated user to be able to use such services.

You do not have to bring provider-specific libraries in order to achieve this, but instead you can use a reactive `Token Propagation` filter, which can be bound to a given REST client with a simple annotation.
For more information, see the Quarkus xref:security-openid-connect-client-reference.adoc#token-propagation-reactive[Access token propagation] guide.

For example, after you have configured the <<google>> provider, you can have events added to the user's Google Calendar by using a REST client as shown in the following example:

[source,java]
----
package org.acme.calendar;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.token.propagation.AccessToken;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@RegisterRestClient(configKey="google-calendar-api")
@AccessToken <1>
@Path("/calendars/primary")
public interface GoogleCalendarClient {
@POST
@Path("events")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
Uni<String> addEvent(Event event);
public static class Event {
public String summary;
public String kind = "calendar#event";
public Time start;
public Time end
}
public static class Time {
public String dateTime;
public String timeZone = "Europe/CET";
public Time() {
}
public Time(String value) {
dateTime = value;
}
}
}
----

<1> The `@AccessToken` annotation enables an access token propagation to the target service.

Finally, you need to configure the Google Calendar address and request the Google Calendar scope for an access token, as outlined in the following example:

[source,properties]
----
quarkus.oidc.authentication.scopes=email,profile,https://www.googleapis.com/auth/calendar
quarkus.rest-client.google-calendar-api.url=https://www.googleapis.com/calendar/v3
----

== References

* xref:security-oidc-code-flow-authentication.adoc[OIDC code flow mechanism for protecting web applications]
Expand Down

0 comments on commit 80826ec

Please sign in to comment.