OpenID Connect Client with Java Undertow

OpenID Connect Client with Java Undertow

Code Examples / website-integration

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.

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 NameValue in tutorial
Client IDdemo-client
Client SecretSecr3t
Scopesopenid, profile
Authorization Grant Typecode
Redirect Urihttp://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-example
cd 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 the dependecies 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.

  • Ppen the App.java file, and replace the code of the main 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();
    }

    @Override
    public 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;
    }

    @Override
    public 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:

login screen

After the login, you will see some contents of the response from the Authorization server:

response

Conclusion

Thanks to pac4j library adding authentication and authorization to a Java app and integrating with the Curity Identity Server is an easy task.

Let’s Stay in Touch!

Get the latest on identity management, API Security and authentication straight to your inbox.

Keep up with our latest articles and how-tos using RSS feeds