This sample application demonstrates how to use OpenID Connect and access tokens to secure an application that makes calls to other applications on behalf of the user.

Microservice-based architecture, described as “a particular way of designing software applications as suites of independently deployable services”, is becoming more common. The result of the transition to this kind of application composition is a lot of network traffic.

A single request from the user can lead to many subsequent requests being made across the collection of services that together implement the app. Each of these subsequent requests should ideally be made in a secure way: you want to know that any given request being handled by any given service originated from a known user. In other words, any given request should be part of the normal expected flow within the application, and not an abuse of your app architecture by someone who noticed that your backend services just happened to also be reachable from wherever they were.

An Overview of OpenID Connect

“The RP has the user talk to the OP which then talks back to the RP which then talks back to the OP before talking to the RS that replies to the RP that then replies to the user.”

Clear? Excellent!

If not, then don’t worry: the only thing really clear is that OpenID Connect seems to love using lots of acronyms (it even abbreviates its own name as OIDC). Let’s start by translating that overview from OpenID-security-speak into something a little more consumable by a human (the definitive source for all this info is really in the OpenID Connect specs, but they can be hard going if you are coming at them cold).

From the user experience point of view, you have the ugly-but-functional browser-provided form used for Basic Auth, and the much better experience of customized login forms with I forgot my password buttons. From the server side, both of these approaches involve validating the the user credentials against some configured data store (database, LDAP, … ). If that data store hasn’t been set up by folks who know what they’re doing, you have a repository full of user credentials that look like a frosted cupcake to a hungry hacker.


“..you have a repository full of user credentials that look like a frosted cupcake to a hungry hacker.”


There has been a realization that you (mostly) don’t (actually) want to look after that data yourself. All you really want to know is: “Who is the user?”. As many of those users already have email accounts or social media accounts that they authorize themselves with regularly, it would be fantastic (for all concerned) to allow the user to use those credentials to log into your web app, instead of them (and you) creating yet another user ID and password to look after.

And that’s where OpenID Connect steps in. It provides a way for applications to verify the identity of their users without being responsible for managing the authentication or authorization of those users.

OpenID Connect requires at least two services: one that acts as the authenticator, and one that relies upon the authenticator. The first is known as the OpenID Provider (OP) and the second is known as the Relying Party (RP). In our scenario, we also have a third service in the mix: the app the user talks to is the RP, but it talks to another service, which we’ll call the Relying Service (RS).

Liberty can be configured to act as an OpenID Provider (that’s the OP), or it can be configured to protect applications as Relying Parties (RPs). Out of the box however, it does not currently offer a built-in way to protect the onward invocation of other services or apps (RS) by the Relying Party application.

Access Token Propagation

OpenID Connect has the concept of an access token that can be queried via the OpenID Provider to retrieve the info about the user who authenticated. This token is one of the artifacts that you end up with after performing an OpenID Connect authentication.

The general sequence of events during the authentication is:

  1. The user accesses the application (RP)
  2. The RP redirects the user to the OpenID Provider (OP) to authenticate
  3. The OP authenticates the user.
  4. The OP redirects the user back to the RP with an auth_token
  5. The RP then exchanges that auth_token with the OP for an access_token and an id_token
  6. Profit!
OP/RP overview image.

(For more information on this flow, see the Knowledge Center)

This sequence may seem complex but odds are you’ve already been through it if you have ever signed into a site using your Facebook, Google, or Twitter credentials:

  • First you went to the app (the RP) (let’s imagine it’s a discussion forum) and clicked the Sign in with Facebook button.
  • That sent you to a Facebook page (the OP).
  • There, you authenticated and possibly agreed to which types of access you wished the app to have to your Facebook profile.
  • And you were then returned to the discussion forum, signed in with your Facebook ID. The last part of the flow, which is the forum exchanging the auth_token for the access_token and id_token, is not visible to you.

While the authentication/authorization has been delegated to a third party, the application still has a notion of who you are, a user ID, username, handle.. you know the drill.


“.. the authentication/authorization has been delegated to a third party, the application still has a notion of who “you” are.”


Our hypothetical forum application needs to invoke other microservices on behalf of the user to obtain private messages for the user or to build the list of unread posts. In each case, the other service needs to know the identity of the user and possibly which types of information (aka scopes) the user agreed to share when they authenticated.

While the forum application (RP) could just pass the user ID along to each of the other microservices, this would be very insecure. There would be no way for the target microservices to check if the user ID they received matched the user that made the initial request, or to know if that user was even still logged in.

Instead of passing the user ID, the RP could forward the access_token obtained during the initial interaction with the OP when it invokes other services. Each service instance (RS) could then contact the OP with the access_token to obtain their own version of the id_token, which will relay to them the user’s identity and scopes. With this, the service can then tailor the response for that user without ever having to authenticate the user themselves.

Note the general flow: each service receives the access_token (this could be the RP, or a subsequent service in the invocation chain), and contacts the OP to exchange that for an id_token. This approach has a benefit, in that each service has an opportunity to notice that the access_token has expired and react appropriately. It also has a flaw, in that communication with the OP can become a bottleneck.

Consideration must be made as to how the application (overall) will handle the impact of a token expiring or being revoked in the ‘middle’ of a request. Handling conditions like this gracefully can have a significant impact on application complexity.

About the sample

We’ve devised a sample application (available on Github) to demonstrate how you can propagate access tokens. It has three Liberty servers configured, each performing one of the three roles of OpenID Provider (OP), Relying Party (RP), and Relying Service (RS). It is possible to have these roles combined in a single server but we’ve separated them out to avoid role confusion.

OpenID Provider (OP)

We’ve created a sample OpenID Provider (OP) to demonstrate what that actor does without relying on watching network traffic going to the usual suspects (Facebook, Google, Twitter, … ).

User registry

The OP has the job of authenticating the users, and Liberty provides a simple way to define a basic user registry right within the server.xml, which is good enough for our purposes.

The server configuration we need for this sample is in the ‘access-token-op-wlpcfg’ project:

    <!-- This is our example user registry.. it has just the one user --> 
    <basicRegistry id="basic" realm="BasicRealm"> 
        <user name="user" password="password" /> 
    </basicRegistry>
  

This snippet defines a user registry with just one user called user with a password of password. Clearly this isn’t intended for production! Liberty can be configured to other methods for authenticating (like LDAP, for example), but we’re trying to focus on access token propagation here, so we’ll stick with the simple way to keep things simple.

The basicRegistry config element requires the appSecurity feature to be present, so we add this to our featureManager block in the config:

    <feature>appSecurity-2.0</feature>
  

OAuth provider

We need to configure the server as an OAuth provider, configuring which clients are allowed to connect to the OP for authentication. In this example we have only two direct clients of the OP: the RP (our web app) and the RS (the service the app will talk to):

    <!-- For simplicity, we're using a client db defined here in the 
          server.xml. Here we register the clients that will access the 
          provider. For this example, that's the RP, and the RS. 
      --> 
    <oauthProvider id="Oauth"> 
        <localStore> 
            <client name="rp" 
                secret="fish" 
                displayname="The user facing webapp" 
                redirect="https://127.0.0.1:9401/oidcclient/redirect/RP"
                scope="openid stock account" 
                preAuthorizedScope="openid" 
                enabled="true"/> 
            <client name="rs" 
                secret="pies" 
                displayname="The microservice" 
                introspectTokens="true" 
                scope="openid" 
                preAuthorizedScope="openid" 
                enabled="true"/> 
        </localStore> 
    </oauthProvider> 

    <oauth-roles> 
        <authenticated> 
            <special-subject type="ALL_AUTHENTICATED_USERS" /> 
        </authenticated> 
    </oauth-roles> 
  

For each client (the RP and the RS), the name and secret are used to authenticate the client connecting to the OP. We’ll see those again in their respective server configs. Here we’ve left the secrets as plain text but, if you chose to, you could encode them using the securityUtility (see the Knowledge Center here and the Knowledge Center here The redirect URL is the post-authentication redirect target: the OP will redirect the client (usually a browser) there after the user has authenticated. The RS doesn’t need a redirect URL because it’s not being protected by OpenID Connect.

When handling OAuth requests, Liberty will do the work of converting the access_token into an id_token. For RPs, this is nice and convenient but it doesn’t help the RS much. The introspectTokens=true attribute grants the RS the special permission required to use introspection to swap an access_token for an id_token.

We don’t need to explicitly add any features to the server.xml for our OAuth configuration because we’ll be using OpenID Connect, which will automatically bring in the required OAuth functionality for us.

OpenID Connect Provider

Now that we have a user registry and an OAuth provider defined, we need to put the OpenID Connect ribbon on, and configure the server as an OpenID provider.

    <keyStore id="oidckeystore" .... />

    <!-- This is the OpenID Provider configuration. 
           We're using Asymmetric RS256 signing, so we must supply a 
           keystore and identify which key we should sign with. 
    --> 
    <openidConnectProvider id="OP" 
           oauthProviderRef="Oauth" 
           signatureAlgorithm="RS256" 
           keyStoreRef="oidckeystore" 
           keyAliasName="oidckey"> 
    </openidConnectProvider> 
  

There are a few things to explain here:

  • The id attribute will be used as part of the URL for the OpenID Provider. We’ve used OP for obviousness, you’ll see where it ends up in the URLs.
  • The keystoreRef is required because we’ve opted to use asymmetrically signed id_tokens. We also have to declare which keyAliasName from the referenced keystore we’d like to use to sign the tokens.
  • The oauthProviderRef is the reference to the OAuth element we defined above.

In a production configuration, it’s unlikely the clients would be declared like this within the server.xml and, more likely, they will be managed in a database. See the Knowledge Center for more info.

There is no application declared for the OP server. The functionality to act as the OP comes purely from features provided by Liberty itself, as part of the openidConnectServer feature. We also need the appSecurity-2.0 feature to support our basic registry, which brings our total combined featureManager block for the OP to look like this:

    <featureManager>
        <feature>openidConnectServer-1.0</feature>
        <feature>appSecurity-2.0</feature>
    </featureManager>
  

Relying Party (RP)

The Relying Party (RP) is the first point of contact with a user. The app itself is simple and intends to show the chained invocation of a RS with the propagated access token.

The ‘access-token-rp-wlpcfg’ project contains the configuration for the RP, which is simpler than that for the OP:

 
    <!--oidc client config.. 
         We must have a secret to obtain a token, and 
         we need a trust store to verify the signing key of the token from the OP. 
         The EndpointUrls must point back to the OP. 
    --> 
    <openidConnectClient id="RP" 
        clientId="rp" 
        clientSecret="fish" 
        signatureAlgorithm="RS256" 
        trustAliasName="oidckey" 
        trustStoreRef="oidctruststore" 
        scope="openid stock account" 
        authorizationEndpointUrl="https://127.0.0.1:9400/oidc/endpoint/OP/authorize"
        tokenEndpointUrl="https://127.0.0.1:9400/oidc/endpoint/OP/token">
    </openidConnectClient> 
  

More notes:

  • The id is important, it is part of the URL that we register in the OP for the redirect. We’ve chosen the id “RP”, and if you glance back at the oauthProvider config, you’ll see there: https://127.0.0.1:9401/oidcclient/redirect/RP.
  • The clientId and clientSecret are required, and must match the RP’s client definition in the OP.
  • We tell the client to expect asymmetrically signed tokens, and tell it which keystore and key to use to verify that signature by specifying the trustStoreRef and trustAliasName.
  • And, lastly, we configure the authorization and token endpoint URLs for the OP. Note that OP is in the URL (just as we told you it would be). If you aren’t creating your own OpenID Provider, the values here will be obtained via cut and paste.

As with the server, we enable support for openidConnectClient by adding the feature to our featureManager block in the server.xml. We’re also using appSecurity (which brings in SSL) and servlet to host the application:

    <featureManager>
        <feature>servlet-3.1</feature>
        <feature>appSecurity-2.0</feature>
        <feature>openidConnectClient-1.0</feature>
    </featureManager>
  

That just leaves the definition for the app itself:

 
    <!-- Declare the application and the security roles enabled for the app. --> 
    <application type="war" id="access-token-rp-application" name="access-token-rp-application" 
         location="${server.config.dir}/apps/access-token-rp-application.war"> 
        <application-bnd> 
            <security-role name="All Role"> 
                <special-subject type="ALL_AUTHENTICATED_USERS" /> 
            </security-role> 
        </application-bnd> 
    </application> 
  

You’ll note there’s no explicit binding saying protect this app with openID Connect. When you declare an openidConnectClient within your Liberty server configuration, it applies to all apps. You can configure a server to have multiple openidConnectClients that use authFilterRefs to decide which apps are protected by which client definition, see the Knowledge Center for more.

The RP application implementation

The openIdConnectClient configured above handles a lot for the application. By the time the request reaches the application code, the user has already been redirected to the OP, authenticated, and Liberty has performed the token exchange to obtain the id_token and access_token from the OP. This is good stuff! But the service we’ve written needs to call another service. This means we need to do a bit of digging to get back the original token used to authenticate the invocation of the RP.

Thankfully that’s not too tricky. It’s available via the com.ibm.websphere.security.auth.WSSubject interface, as in the following snippet:

        //we were authenticated using OIDC, and we want to obtain the access_token that's part of this session. 
        Subject s; 
        try{ 
             s = WSSubject.getRunAsSubject(); 
        }catch( WSSecurityException e){ 
             throw new IOException(e); 
        } 
          
        Set<Hashtable> privateHashtableCreds = s.getPrivateCredentials(Hashtable.class); 
 
        //there could be many.. we'll just take the one with access_token. 
        Hashtable theChosenOne = null; 
        for(Hashtable test : privateHashtableCreds){ 
         if(test.containsKey("access_token")){ 
           theChosenOne = test; 
         } 
        } 
 
        //now we have found the credentials holding the current access_token 
        //we will cache it locally so we can invoke the RS with it. 
        String access_token = theChosenOne.get("access_token").toString(); 
  

With the token as a string, we can now invoke the RS, which we could do via JAX-RS or any other route, as long as we pass that access token as one of the parameters. There is no standard way to do this, though stuffing it into an HTTP request header is common. Whatever mechanism you decide on should be hardened as part of the RS service API.

The following snippet invokes the request using HttpURLConnection, and adds the access_token as a GET parameter:

        //invoke the RS app, passing the access token as a get parameter 
        String line,buffer=""; 
        URL rsurl = new URL("https://127.0.0.1:9402/access-token-rs-application/Test?token="+access_token);
        HttpURLConnection conn = (HttpURLConnection)rsurl.openConnection(); 
        conn.setRequestMethod("GET"); 
 
        //read back the response from the RS app. 
        int responseCode = conn.getResponseCode(); 
        BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); 
        
        while((line=br.readLine())!=null){ 
         buffer+=line; 
        } 
        br.close(); 
  

In a real application, there would likely be subsequent processing of a real result, including handling error conditions or token revocation/expiry. The response could be anything from JSON to binary data. In our our example, it’s just a string that details the processing the RS carried out as a result of the request.

Relying Service (RS)

So that leaves the Relying Service (RS) which, compared to its sibling servers, looks remarkably spartan. It has the usual SSL keystore elements and then just the app declaration (and in this case, even that is optional; you could chuck this one in dropins).

    <application type="war" id="access-token-rs-application" name="access-token-rs-application" 
         location="${server.config.dir}/apps/access-token-rs-application.war"> 
    </application> 
  

To keep our app configuration in the server.xml and out of the code, we also define our client ID, secret, and the introspection URL to be part of our server configuration. We’ll look these up in the app later:

    <!-- oidc client info for the app -->
    <jndiEntry jndiName="oidcClientId" value="rs"/>
    <jndiEntry jndiName="oidcClientPw" value="pies"/>
    <jndiEntry jndiName="introspectUrl" value="https://127.0.0.1:9400/oidc/endpoint/OP/introspect"/>
  

We’re using the SSL and servlet features to host the RS application. To save ourselves a little work, we’re also using the Liberty JSONP feature for JSON parsing. All that adds up to a featureManager block that looks like this:

    <featureManager>
        <feature>ssl-1.0</feature>
        <feature>jsonp-1.0</feature>
        <feature>servlet-3.1</feature>
    </featureManager>
  

The RS has no server configuration for OpenID Connect because the authentication is all handled in the application code itself.

The RS application implementation

The application has to take the provided access_token, and check it with the OP. To begin, we get the access_token as supplied by the requester (in our example, it’s just a request parameter):

        //obtain the access_token from the request. 
        //in this case, we're passing it as a request parameter. 
        String access_token = request.getParameter("token"); 
  

Then we need to use the introspect URL on the OP to retrieve the id_token. This snippet uses HttpURLConnection to make a POST request to the OP with the access_token as a parameter. The RS authenticates to the OP using its own client ID and secret, which we declared back in the OP’s server xml. For more information on the introspection endpoint, read the Knowledge Center. For an overview of all supported Liberty endpoint URLs when using OpenID Connect, see the Knowledge Center:

We’re using JDNI here to pull the introspection URL, client ID, and secret from the server.xml where we stored them earlier. The values in the server.xml could even have been set as environment variables, enabling the RS to be pushed out to cloud style deployments without altering the server.xml:

                //get our client info from the server.xml
                String clientid = null;
                String clientsecret = null;
                String introspectUrl = null;
                try{
                        clientid = new InitialContext().lookup("oidcClientId").toString();
                        clientsecret = new InitialContext().lookup("oidcClientPw").toString();
                        introspectUrl = new InitialContext().lookup("introspectUrl").toString();
                }catch(NamingException e){
                        throw new IOException(e);
                }

                //call the OP with the access_token to introspect the id info.
                String line,buffer="";
                URL introspectourl = new URL(introspectUrl);
                HttpURLConnection conn = (HttpURLConnection)introspectourl.openConnection();
                conn.setRequestMethod("POST");

                //Basic auth, using clientid & secret for microservice1 in OP
                String auth = clientid+":"+clientsecret;
                String basic = "Basic "+DatatypeConverter.printBase64Binary(auth.getBytes());

                //add the Auth header.
                conn.setRequestProperty("Authorization",basic);

                //invoke the request, sending the access_token as the post paramter 'token'
                conn.setDoOutput(true);
                OutputStreamWriter wrToken = new OutputStreamWriter(conn.getOutputStream());
                wrToken.write("token=" + access_token);
                wrToken.flush();
                wrToken.close(); 
  

That much is enough to transmit the request. It’s a lot shorter if you use a library like HttpClient rather than HttpURLConnection but the basics are the same.

After sending the request, we need to read back the token response and act upon it, like so:

        //take the result back as a json object. 
        InputStream is = conn.getInputStream(); 
        JsonReader jsonReader = Json.createReader(is); 
        JsonObject obj = jsonReader.readObject(); 
        jsonReader.close(); 
        is.close(); 
 
        //query the result to test if the token is still active. 
        //it may have expired if the request has been sent using a cached 
        //token, the OP tells us if the access_token  is still active in the response. 
        Boolean isActive = obj.getBoolean("active"); 
  

The response to an introspection request is a JSON object with various fields, only one of which is interesting to us at this stage: the active field of the JSON object will state if the access_token is still valid or not. Here we’re using the jsonp-1.0 feature in Liberty to parse the JSON response from the OP and pull out the active field so we can act upon it.

Finally, we can perform our regular RS processing (or not):

        if(isActive){ 
            out.println("Token is still active.<br>"); 
            out.println("Subject : "+obj.getString("sub")+"<br>"); 
            out.println("  Scope : "+obj.getString("scope")+"<br>"); 
 
            //here we could now filter our processing on if a given scope is present 
            //in the scope set. 
 
            //instead we'll just dump the full token, so you can see all the other info 
            //that came back from the introspection. 
            out.println("Full Introspection Response : "+obj.toString()); 
 
        } else{ 
            out.println("Token has expired. RS will not do further processing"); 
        } 
  

As this is just an example of how to propagate identity using the access_token, we don’t really have any processing to do, so we just dump a little info on the
token to our output. If the token is expired, we output a message saying that instead.

With a little work, you could easily move this logic out into a ServletWrapper, leaving the app itself to focus on its own logic.

A word or two on keys and keystores

This sample is using both SSL and signed ID tokens (as part of the OpenID Connect flows). As such, we end up having to play with SSL certificates and keys. We have quite a few of them being used by the three servers:

  • Keystores used by the OpenID Provider server:
    • OpenID Connect KeyStore: Contains the private key used by the Provider to sign the id_tokens returned to Relying Parties.
    • OpenID Provider KeyStore: Contains the private key used by the Provider to protect it’s SSL communications.
  • Keystores used by the Relying Party server:
    • Relying Party KeyStore: Contains the private key used by the Relying Party to protect it’s SSL communications.
    • OpenID Provider TrustStore: Contains the public key used by the Relying Party to verify the signatures on the id_tokens from the OpenID Provider.
    • Relying Party TrustStore: Contains the public keys for the Relying Service SSL, and for the OpenID Provider SSL.
  • Keystores used by the Relying Service server:
  • Relying Service KeyStore: Contains the private key used to protect its SSL communications.
  • Relying Service TrustStore: Contains the public key for the OP SSL, allowing the app to communicate with the OP.

Building the sample with Gradle will create each of the keystores and export/import the public keys as required. This ensures that the keys have expiration dates that are still valid when the sample is created. In a production environment, it’s unlikely that the SSL keystores will have self-signed certificates. Open ID keystores should also contain certificates signed by a trusted root authority.

Gradle

Having three servers and two apps makes for lots of fun in compiling, deploying, and testing, but thankfully, we’ve written some Gradle to glue it all together.

Running gradle build against the parent folder will download Liberty, install the required features (we’re taking the minimum sets here), create the keystores, compile the code into WAR applications, and deploy the applications to the servers.

Some setup is required:

  1. For the keystore generation, we must set JAVA_HOME to point at our JDF, else the Gradle won’t be able to find the keytool binary it needs to invoke to generate the keystores.
  2. For downloading Liberty to work, we need to add the license code into the gradle.properties file. We can’t put that in there by default, as placing of the license code into the properties indicates that the license has been read and accepted. You can obtain the license code by reading the current license and looking for the D/N: line.

Once you’ve done that, enter the command:

gradle build publishToMavenLocal 

That builds all the code and deploy the applications. If you then want to test the application, enter:

gradle access-token:start

That starts the three servers (OP,RP,RS) that form part of the access-token sample. Similarly, to shut down the servers:

gradle access-token:stop

While the servers are running you can visit:

https://127.0.0.1:9401/access-token-rp-application

That starts you on the whole authenticate via the OP process. Sign in using the user ID user and the password password as we configured back in the OP server.xml.

5 comments on"Using access tokens to secure microservices"

  1. […] Using access tokens to secure microservices is the first of two from Ozzy. He explains how the access tokens created as a result of working with OAuth and OpenID Connect work and are used within a microservices architecture. […]

  2. What if I have an application that in some scenarios act as a RP, and in other scenarios act as a RS, can I configure liberty and openidconnect to support this? Or do I have to break my application up into several pieces with more distinct roles (RP vs RS)?

  3. Thanks for a great article! How does this interact with the ltpa token/mechanism?

    • OpenID Connect and LTPA tokens are distinct single sign-on constructs in Liberty. They are both mechanisms used for granting access to protected resources, however they do not necessarily share any kind of interaction.

      When authenticating to a Liberty server acting as an OpenID Provider (OP), an LTPA token is generated for that particular user to facilitate single sign-on with the OP. When authenticating to a Liberty server acting as an OpenID Connect Relying Party (RP), an LTPA token may be generated for subsequent request to the RP. However, that LTPA token (the one generated for the RP) behaves more like a session token and cannot be shared with other servers (the token is returned using a different cookie name and might have been generated using a different LTPA key which could render it useless to other servers).

      • Hi ayoho,

        We configured our websphere as OpenID connect RP. It generates a JsessionID as well as an WAS_* cookie (which I believe is the LTPA token). Is it possible to turn off LTPA cookie and rely solely on the JsessionId to identity the user session? Thanks.

Join The Discussion

Your email address will not be published. Required fields are marked *