OpenID Connect Client with Java Undertow
On this page
This tutorial shows how to create a basic Java application using Undertow with endpoints allowing you to login a user using integration with the Curity Identity Server. Pac4j library will be used as the OIDC client.
Note
The example uses the Curity Identity Server, but you can run the code against any standards-based authorization server.
Prerequisites
Curity Identity Server
Make sure you configure a client in the Curity Identity Server before getting started. You must be familiar with the following details:
- client id
- client authentication and client secret
- scopes
- authorization grant type (capability)
- redirect uri
To configure a new client follow the tutorial here: Configure a Client
It is assumed that the application will be deployed locally which is reflected in the redirect uri. The following values will be used:
Parameter Name | Value in tutorial |
---|---|
Client ID | demo-client |
Client Secret | Secr3t |
Scopes | openid, profile |
Authorization Grant Type | code |
Redirect Uri | http://localhost:8080/callback |
Instead of creating the client manually you can merge the following xml with your current configuration.
<config xmlns="http://tail-f.com/ns/config/1.0"><profiles xmlns="https://curity.se/ns/conf/base"><profile><id>my-oauth-profile</id> <!-- Replace with the ID of your OAuth profile --><type xmlns:as="https://curity.se/ns/conf/profile/oauth">as:oauth-service</type><settings><authorization-server xmlns="https://curity.se/ns/conf/profile/oauth"><client-store><config-backed><client><id>demo-client</id><client-name>Java app demo</client-name><secret>Secr3t</secret><redirect-uris>http://localhost:8080/callback</redirect-uris><scope>openid</scope><scope>profile</scope><capabilities><code/></capabilities><use-pairwise-subject-identifiers><sector-identifier>demo-client</sector-identifier></use-pairwise-subject-identifiers><validate-port-on-loopback-interfaces>true</validate-port-on-loopback-interfaces></client></config-backed></client-store></authorization-server></settings></profile></profiles></config>
The client uses Pairwise Subject Identifier
The configuration in the XML above has the Pairwise Subject Identifiers (or PPID) option enabled. PPIDs are a way of increasing privacy of your users. The tokens issued to a client do not use the user's ID, but instead a pseudonymous, opaque ID. This means that you'll see a random string in the sub
claim of your tokens. If, for some reason, you need the original user ID in this claim, turn this feature off (or delete the <use-pairwise-subject-identifiers>
tag from the XML above). We highly recommend keeping this feature on, though.
Have a look at ppid if you want to learn more about PPIDs and their importance.
For this tutorial, the OpenID Connect metadata of the Curity Identity Server must be published. This metadata will be used to load some configuration settings by the pac4j library. The Curity Identity Server publishes the metadata at {issuerUri}/.well-known/openid-configuration
. In this tutorial the following value will be used for the issuer: https://idsvr.example.com/oauth/v2/oauth-anonymous
.
Java and Gradle installed
In this tutorial Gradle is used for automating some tasks, so make sure you have gradle installed. If you prefer to use Maven you will have to update some commands to reflect this. Alternatively you can download the complete code for this tutorial using the GitHub button on this page, if you don't want to install Gradle. The example code comes with a Gradle wrapper.
Create a Java website
Initialize a Java app
Create the new app by following these steps:
- Create an empty directory for the app, e.g.
java-undertow-example
mkdir java-undertow-examplecd java-undertow-example
- Init a gradle application and fill in your settings as prompted, e.g. the package name or the name of the application.
gradle init
When prompted for project type, choose application
, then language Java
. You can leave the other choices default.
- Install dependencies. Undertow and pac4j will be used in this tutorial. Add the following lines to the
build.gradle
file, in thedependecies
method.
implementation 'io.undertow:undertow-core:2.2.7.Final'implementation 'org.pac4j:pac4j-oidc:4.4.0'implementation 'org.pac4j:undertow-pac4j:4.1.1'
undertow
is an embedded web server, so that your application can expose endpoints and serve content.pac4j
is a library which handles OpenID Connect and OAuth flows with simple code.undertow-pac4j
is a bridge library which makes using pac4j with Undertow simpler.
Let's move on to creating the app itself.
- Open the
App.java
file, and replace the code of themain
method to the following:
OidcConfiguration oidcConfiguration = new OidcConfiguration();oidcConfiguration.setClientId("demo-client");oidcConfiguration.setSecret("Secr3t");oidcConfiguration.setDiscoveryURI("https://idsvr.example.com/oauth/v2/oauth-anonymous/.well-known/openid-configuration);oidcConfiguration.setResponseType("code");oidcConfiguration.setScope("openid profile");oidcConfiguration.setUseNonce(true);oidcConfiguration.setWithState(true);OidcClient<OidcConfiguration> oidcClient = new OidcClient<>(oidcConfiguration);oidcClient.setName("curity-client");Config config = new Config("http://localhost:8080/callback", oidcClient);LoginHandler loginHandler = new LoginHandler(config);UserHandler userHandler = new UserHandler(loginHandler);RoutingHandler routes = new RoutingHandler().get("/login", loginHandler).get("/callback", CallbackHandler.build(config, "/")).get("/", userHandler).setFallbackHandler(exchange -> exchange.setStatusCode(404));Undertow server = Undertow.builder().addHttpListener(8080, "localhost",new SessionAttachmentHandler(routes, new InMemorySessionManager("session-manager"), new SessionCookieConfig())).build();server.start();
This code configures and starts the Undertow server. Note, how in line 20 the callback handler from the undertow-pac4j
library is used. Thanks to this, you don't have to handle the callback yourself.
- Create a
LoginHandler
class in your source folder. This handler will be responsible for initiating the login flow. Put the following code in the class:
import io.undertow.server.HttpHandler;import io.undertow.server.HttpServerExchange;import org.pac4j.core.config.Config;import org.pac4j.core.exception.http.RedirectionAction;import org.pac4j.oidc.client.OidcClient;import org.pac4j.oidc.config.OidcConfiguration;import org.pac4j.undertow.context.UndertowWebContext;import org.pac4j.undertow.http.UndertowHttpActionAdapter;import java.util.Optional;public class LoginHandler implements HttpHandler{private final OidcClient<OidcConfiguration> _oidcClient;public LoginHandler(Config config){_oidcClient = (OidcClient<OidcConfiguration>) config.getClients().findClient("curity-client").get();}@Overridepublic void handleRequest(HttpServerExchange exchange) throws Exception{UndertowWebContext context = new UndertowWebContext(exchange);Optional<RedirectionAction> action = _oidcClient.getRedirectionAction(context);UndertowHttpActionAdapter.INSTANCE.adapt(action.get(), context);}}
- Next, create a
UserHandler
class. This one will print out the user's login and obtained access token. If the user is not authenticated, this handler will redirect to login. Put the following code in the class:
import io.undertow.server.HttpHandler;import io.undertow.server.HttpServerExchange;import io.undertow.util.HttpString;import org.pac4j.core.context.WebContext;import org.pac4j.oidc.profile.OidcProfile;import org.pac4j.undertow.context.UndertowWebContext;import java.util.HashMap;import java.util.Optional;import static org.pac4j.core.util.Pac4jConstants.USER_PROFILES;public class UserHandler implements HttpHandler{private final HttpHandler _loginHandler;public UserHandler(HttpHandler loginHandler){_loginHandler = loginHandler;}@Overridepublic void handleRequest(HttpServerExchange exchange) throws Exception{WebContext context = new UndertowWebContext(exchange);Optional<HashMap<String, OidcProfile>> optionalUserProfile = context.getSessionStore().get(context, USER_PROFILES);if (optionalUserProfile.isPresent()) {OidcProfile profile = optionalUserProfile.get().get("curity-client");exchange.getResponseHeaders().put(HttpString.tryFromString("Content-Type"), "application/json");String response = "{" +"\"subject\": \"" + profile.getSubject() + "\"," +"\"accessToken\": \"" + profile.getAccessToken() + "\"" +"}";exchange.getResponseSender().send(response);} else {_loginHandler.handleRequest(exchange);}}}
- Start the app:
gradle run
Note: If you're using the Gradle wrapper, use the command ./gradlew run
, or gradlew.bat run
if you're using Windows.
If everything went as planned, you should see in your console the log of your app starting. You can now open the browser and go to http://localhost:8080
. As you're not authenticated, you will be redirected to the authorization endpoint of your Authorization Server. Once you log in, you should see your user's login (might be pseudonymized as noted earlier) and an access token.
First you will see the login screen:
After the login, you will see some contents of the response from the Authorization server:
Conclusion
Thanks to pac4j library adding authentication and authorization to a Java app and integrating with the Curity Identity Server is an easy task.
Join our Newsletter
Get the latest on identity management, API Security and authentication straight to your inbox.
Start Free Trial
Try the Curity Identity Server for Free. Get up and running in 10 minutes.
Start Free Trial