Quarkus - OIDC Proxy
Introduction
OIDC Proxy extends Quarkus OIDC extension and adds OIDC authorization code flow support for Quarkus OIDC service
applications by proxying OIDC authorization code flow requests and delegating them to the real OIDC provider which is configured for the current Quarkus OIDC service
application.
It allows an integration of Quarkus OIDC service
applications with the external Single-page applications (SPA) or Quarkus OIDC web-app
applications which authenticate users with the OIDC authorization code without exposing internal OIDC configuration details.
OIDC Proxy can also help when external SPAs can not implement an authorization code flow with some OIDC and OAuth2 providers but which are supported at the Quarkus OIDC level.
Installation
Add this Maven dependency:
<dependency>
<groupId>io.quarkiverse.oidc-proxy</groupId>
<artifactId>quarkus-oidc-proxy</artifactId>
</dependency>
Getting Started
Adding the OIDC proxy dependency enables the SPA to use the OIDC proxy endpoints to authenticate users with an authorization code flow and send the acquired access tokens to the Quarkus OIDC service
application.
For example, if the endpoint is listening at http://localhost:8080
, then, after adding this dependency, the OIDC authorization endpoint is available at http://localhost:8080/q/oidc/authorize
, the OIDC token endpoint is available at http://localhost:8080/q/oidc/token
, the OIDC JWKS endpoint is available at http://localhost:8080/q/oidc/jwks
and finally, the OIDC well known configuration endpoint which supports the discovery is available at http://localhost:8080/q/oidc/.well-known/openid-configuration
.
For example, the following configuration is enabling an OIDC proxy over a Quarkus OIDC Auth0 service
application, without having to configure your Auth0 application to allow redirects to the SPA page:
quarkus.oidc.auth-server-url=https://${auth0-dev}.us.auth0.com (1)
quarkus.oidc.client-id=${auth0-client-id}
quarkus.oidc.credentials.secret=${auth0-client-secret}
quarkus.oidc.authentication.redirect-path=/q/oidc/callback (2)
quarkus.oidc-proxy.external-redirect-uri=${external-spa-redirect-url}
1 | OIDC service application which can only accept and verify the bearer access tokens. |
2 | Let OIDC proxy accept callbacks at the /q/oidc/callback path and redirect to the actual SPA redirect path. The Auth0 application will only need to allow a redirect to http://localhost:8080/q/oidc/callback . |
OIDC proxy root and individual endpoint paths can be customized.
You can customize the /q/oidc
OIDC proxy root path with a quarkus.oidc-proxy.root-path
property. Each individual endpoint can also be customized.
For example, if you set quarkus.oidc-proxy.root-path
to openid-connect
and quarkus.oidc-proxy.authorization-path
to /authorization
, then you will get the OIDC authorization endpoint available at http://localhost:8080/openid-connect/authorization
, etc.
OIDC proxy endpoints are public because they have to delegate to the real OIDC provider.
If you use a wildcard authentication or role-based access control HTTP policy, make sure to permit access to the OIDC proxy endpoints, for example:
quarkus.http.auth.permission.service.paths=/* (1)
quarkus.http.auth.permission.service.policy=authenticated
quarkus.http.auth.permission.oidcproxy.paths=/q/oidc/* (2)
quarkus.http.auth.permission.oidcproxy.policy=permit
1 | Requests to all service endpoints and resources must be authenticated |
2 | Permit access to the OIDC proxy endpoints only |
Testing
One relatively simple way to test OIDC Proxy is to use HtmlUnit
to test a Quarkus OIDC web-app
endpoint which is configured to use OIDC provider whose endpoints point to the OIDC Proxy.
OIDC Proxy will delegate to Keycloak
to manage an authorization code flow for this Quarkus OIDC web-app
endpoint. Keycloak is launched by DevServices for Keycloak which support both dev and integration test modes.
The Quarkus OIDC web-app
endpoint will use an access token provided by the OIDC Proxy to call the OIDC service
endpoint.
Here is how you can create such a test.
Start by adding the following dependencies to your test project:
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
Write this test code:
package io.quarkus.oidc.proxy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import com.gargoylesoftware.htmlunit.SilentCssErrorHandler;
import com.gargoylesoftware.htmlunit.TextPage;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class OidcProxyTestCase {
@Test
public void testOidcProxy() throws Exception {
try (final WebClient webClient = createWebClient()) {
HtmlPage page = webClient.getPage("http://localhost:8081/web-app");
assertEquals("Sign in to quarkus", page.getTitleText());
HtmlForm loginForm = page.getForms().get(0);
loginForm.getInputByName("username").setValueAttribute("alice");
loginForm.getInputByName("password").setValueAttribute("alice");
TextPage textPage = loginForm.getInputByName("login").click();
assertEquals("web-app: ID alice, service: Bearer alice", textPage.getContent());
webClient.getCookieManager().clearCookies();
}
}
private WebClient createWebClient() {
WebClient webClient = new WebClient();
webClient.setCssErrorHandler(new SilentCssErrorHandler());
return webClient;
}
}
Add an OIDC web-app
endpoint which will require an authorization code flow to support HtmlUnit calls and propagate the access token to the service endpoint:
package io.quarkus.oidc.proxy;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import io.quarkus.oidc.IdToken;
import io.quarkus.security.Authenticated;
import io.smallrye.mutiny.Uni;
@Path("/web-app")
@Authenticated
public class OidcWebAppResource {
@Inject
@IdToken
JsonWebToken idToken;
@Inject
@RestClient
ServiceApiClient serviceApiClient;
@GET
@Produces("text/plain")
public Uni<String> getName() {
return serviceApiClient.getName().onItem()
.transform(c -> ("web-app: " + idToken.getClaim("typ") + " " + idToken.getName() + ", service: " + c));
}
}
Add ServiceApiClient
which OidcWebAppResource
will call:
package io.quarkus.oidc.proxy; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; import jakarta.ws.rs.core.MediaType; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.quarkus.oidc.token.propagation.AccessToken; import io.smallrye.mutiny.Uni; @RegisterRestClient(configKey = "service-api-client") @AccessToken public interface ServiceApiClient { @GET @Consumes(MediaType.TEXT_PLAIN) Uni<String> getName(); }
An OIDC service
endpoint may look like this:
package io.quarkus.oidc.proxy;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import org.eclipse.microprofile.jwt.JsonWebToken;
import io.quarkus.security.Authenticated;
@Path("/service")
@Authenticated
public class OidcServiceResource {
@Inject
JsonWebToken accessToken;
@GET
@Produces("text/plain")
public String getName() {
return accessToken.getClaim("typ") + " " + accessToken.getName();
}
}
Finally, configure application.properties
, note that DevServices for Keycloak
will configure the OIDC service
endpoint by setting its quarkus.oidc.auth-server-url
, quarkus.oidc.client-id
and quarkus.oidc.credentials.secret
properties.
# Default OIDC tenant which supports the OIDC `service` endpoint is setup by DevServices for Keycloak
# The OIDC `web-app` tenant supports the OIDC `web-app` endpoint
quarkus.oidc.web-app.auth-server-url=http://localhost:8081/q/oidc (1)
quarkus.oidc.web-app.client-id=${quarkus.oidc.client-id}
quarkus.oidc.web-app.credentials.secret=secret
quarkus.oidc.web-app.application-type=web-app
quarkus.oidc.web-app.authentication.cookie-path=/web-app (2)
quarkus.rest-client.service-api-client.url=http://localhost:8081/service
1 | OIDC Proxy sets all the required routes using the /q/oidc root path, as explained in the Getting Started section. All the individial endpoint addresses are auto-discovered but you can also configure them individually. |
2 | OIDC session cookie path is limited to the OIDC web-app endpoint path only to avoid it being recognized during the OIDC web-app endpoint propagating the access tokens to the service endpoint. It is only done to support this test setup but is not necessary if the service endpoint is not collocated with the web-app endpoint. |
Next, once the integration test passes with these application.properties
, you can add more tests verifying other OIDC Proxy properties.
Extension Configuration Reference
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Type |
Default |
|
---|---|---|
If the OIDC Proxy extension is enabled. Environment variable: |
boolean |
|
OIDC service tenant identifier which can be set to select an OIDC tenant configuration. The default OIDC tenant configuration is used when this property is not set. Environment variable: |
string |
|
OIDC proxy root path. Environment variable: |
string |
|
OIDC proxy authorization endpoint path relative to the Environment variable: |
string |
|
OIDC proxy token endpoint path relative to the Environment variable: |
string |
|
OIDC proxy JSON Web Key Set endpoint path relative to the Environment variable: |
string |
|
OIDC proxy UserInfo endpoint path relative to the Environment variable: |
string |
|
Allow to return an ID token from the authorization code grant response. Environment variable: |
boolean |
|
Allow to return a refresh token from the authorization code grant response. Environment variable: |
boolean |
|
Absolute external redirect URI. If 'quarkus.oidc.authentication.redirect-path' is configured then configuring this property is required. In this case, the proxy will request a redirect to 'quarkus.oidc.authentication.redirect-path' and will redirect further to the external redirect URI. Environment variable: |
string |
|
Client id that the external client must use. If this property is not set then the external client must provide a client_id which matches Environment variable: |
string |
|
Client secret that the external client must use. If this property is not set then the external client must provide a client_secret which matches the configured OIDC service client secret. External client may not provide the client_secret if it is not configured with either this property or the OIDC tenant configuration, in order to support public clients. Environment variable: |
string |