Financial-Grade Token Handler

Financial-Grade Token Handler

Overview

The role of the main token handler API (the OAuth agent in the below diagram) is to handle the OpenID Connect flow for Single Page Applications (SPA). This includes issuing of secure cookies for the browser and managing follow-on operations for token refresh, retrieving user info and signing out:

Token Handler

This implementation is coded in Kotlin and implements OpenID Connect using the following three financial-grade features. The SPA then uses only the strongest HTTP Only, SameSite=strict cookies in the browser, while JWT access tokens are used in a standard way by APIs.

Token Handler Operations

The following operations represent the Token Handler’s API interface, and all of these are designed to serve SPAs via Ajax requests from the browser. This enables the SPA to fully control behavior such as when redirects occur:

EndpointDescription
POST /login/startStart a login by providing the request URL to the SPA and setting temporary cookies
POST /login/endValidate the request, complete the login and return HTTP only encrypted cookies to the browser
GET /userInfoReturn Personally Identifiable Information (PII), such as the user name, to the SPA
POST /refreshAsk the token handler to refresh the current access token and rewrite secure cookies
POST /logoutAsk the token handler to clear secure cookies and return an end session request URL

A token handler is a tricky API to develop, since its main focus is on browser and OAuth infrastructure:

BehaviorDescription
Authorization CodesThe API needs to receive real authorization codes in order to get tokens
HTTP RedirectsThe SPA client therefore needs to perform real HTTP redirects and user logins
Cross Origin RequestsThe API and its SPA client must both deal with Cross Origin Request Sharing (CORS)
CookiesThe API operations need to frequently read, write and update encrypted cookies containing tokens

API Driven Development

When working on the token handler as an API it is useful to initially avoid a browser and use a test driven approach. In the following sections we will show how to do this for the token handler API. First get the code, which contains the following resources:

Token Handler Resources

Running Integration Tests

The token handler comes with a suite of Spock integration tests which make API calls to the handler. Wiremock is used in the test suite to mock responses from the Authorization Server. You can run these tests by issuing ./gradlew test command or straight from an IDE. These tests do not require any external dependencies or environment settings and are a good starting point when working with the handler.

Tests Using an Authorization Server Instance

Follow the instructions below to test the token handler using an instance of the Curity Identity Server instead of mocked responses. This option can prove useful for manual testing of the token handler features. Curity has also provided a curl-based client which runs the usual flows.

Prerequisites

Next ensure that the following components are installed:

You will also need a license file for the Curity Identity Server with support for financial-grade features. This will be the case if you have a trial license.

URLs and Certificates

When running on a development computer, the token handler is configured to use the following base URLs:

ComponentBase URL
Token Handlerhttps://api.example.local:8080/tokenhandler
Curity Identity Server Endpointshttps://login.example.local:8443
Curity Identity Server Admin UIhttps://localhost:6749/admin

For this to work you must first add the following entries to the local computer’s hosts file:

127.0.0.1  api.example.local login.example.local
:1 localhost

Next run the following commands, which execute a script that uses OpenSSL to generate local development certificates:

cd certs
./create-certs.sh

The following client and server certificates are then created, along with a root certificate authority, and they will be used by the token handler and Authorization Server:

Token Handler Certificates

Finally configure the Java runtime to trust the root certificate authority, via a command that points to your Java SDK:

sudo "$JAVA_HOME/bin/keytool" -import -alias example.ca -cacerts -file ./certs/example.ca.pem -storepass changeit -noprompt

Run the Token Handler API

Run the API from the command line via gradle, or use your preferred Java IDE:

./gradlew bootRun

You can then simulate a request from the SPA via the following test command, to ensure that connectivity is working:

curl -X POST https://api.example.local:8080/tokenhandler/login/start \
-H "origin: https://www.example.local" | jq

Token Handler Security Code

The OAuth logic for the token handler API can be studied and adapted if needed. The controller source files provide an outline of processing, including cookie issuing. To understand the main interaction with the Authorization Server, see the AuthorizationServerClient class:

Financial Logic

Run the Authorization Server

This is done by first copying a license file into the test/idsvr folder and then running the deploy script, which will spin up the Curity Identity Server in a Docker container:

cd test/idsvr
./deploy.sh

You can then log in to the Admin UI with credentials admin / Password1 to view client configuration details. OAuth requests on behalf of the SPA are initiated from the token handler, which uses Mutual TLS client authentication when it calls the Authorization Server:

Mutual-TLS Client

Later, when you have finished with the token handler, you can free all Docker resources by running this script:

cd test/idsvr
./teardown.sh

Run an API Test Client

During token handler development it can be useful to use a test client that sends the same HTTP requests as an SPA, but avoids the need to continually switch to a browser and extract values such as authorization codes. Curity have provided a script based client using the popular curl and jq tools, and it is run via the following commands:

cd test
./test-token-handler.sh

The script runs a complete SPA workflow, using a preconfigured user account shipped with the Docker deployment. This begins with a login, then continues with refresh and user info requests, finishing with a logout. This fast feedback will enable any coding bugs in the token handler to be quickly found and ironed out:

Financial-grade Token Handler Workflow

OpenID Connect Messages

The token handler provides front channel requests to the SPA by creating and returning a URL. The token handler gets this URL by posting a pushed authorization request to the Authorization Server, containing the standard OpenID Connect parameters:

POST https://login.example.local:8443/oauth/v2/oauth-authorize/par

client_id=spa-client&
redirect_uri=http%3A%2F%2Fwww.example.com%2F&
scope=openid%20profile&
response_type=code&
response_mode=jwt&
code_challenge=k2XFDLJ_mIVEt25-phu4_3O4Jqrd8QqTGGbeEsql3Rc&
code_challenge_method=S256&
state=NlAoISfdL1DxPdNGFBljlVuB1GDjgGARmqDcxtHhV8iKNYu6ECS2KOavDHpI3eLN

The result is a URL of the following form, where PAR protects against potential request tampering in the browser. This URL is returned to the SPA client, which has no knowledge of OAuth security details, and simply redirects on the request URL it is given:

https://login.example.local:8443/oauth/v2/oauth-authorize?
client_id=spa-client&
request_uri=urn:ietf:params:oauth:request_uri:7d353fc8-9b94-488f-8c61-cf7cc1dfef9e"

When a login completes, the SPA receives a JWT query parameter containing the code and state. Use of JARM protects against potential response tampering in the browser. The SPA then posts this URL to the token handler:

{
  "exp": 1637593781,
  "iss": "https://login.example.local:8443/oauth/v2/oauth-anonymous",
  "aud": "spa-client",
  "iat": 1637593761,
  "purpose": "authz_response",
  "code": "dLjNJphJveReaQflxvl2oUTxCxxeDguk",
  "state": "kl9kbxyMFnzXpgzgDsVYqiYZ7YC1sHwpkSzrEbE7ygfTqmXAUwLeb9mq7BO5QqsI",
  "session_state": "/pE6l/goEZJOawote6lqdYUjNZTKI4lNMFnPJNOM4XU=.LmJ6oN371McY"
}

The token handler deals with validating the response JWT and extracting the code and state parameters. After validation checks the flow continues in the standard way, by redeeming the code for tokens. This is done via a back channel request to the Authorization Server, which again uses Mutual TLS to authenticate the client:

POST https://login.example.local:8443/oauth/v2/oauth-token

grant_type=authorization_code&
client_id=spa-client&
authorization_code=bMYnHhHHRFRnT7sbuPmqIzHpb8RFfqtg&
redirect_uri=http://www.example.com/

Tokens returned in the response are then stored in HTTP Only encrypted secure cookies that are returned to the browser but not accessible from Javascript code. AES256 symmetric encryption is used to encrypt the cookies, with a key only known to the token handler.

Browser Integration

Once the API is working as expected and any code changes have been completed, you can then deploy it and test with real SPAs running in the browser. This is simply a case of pointing the SPA to the base URL of the token handler in the deployed environment:

{
    "businessApiBaseUrl": "https://api.example.com/api",
    "oauth": {
        "tokenHandlerBaseUrl": "https://api.example.com/tokenhandler"
    }
}

See the following articles for further details on running a fully integrated solution:

ArticleDescription
SPA Code ExampleAn end-to-end setup for a development computer, to run an example SPA that uses this token handler
End-to-End TutorialFurther details on the end-to-end setup, including deployment and infrastructure

Conclusion

This code example showed how the token handler pattern can be used to extend an SPA with financial-grade website security features. If required companies can further adapt Curity’s token handler implementation for their own purposes, by following the API development steps in this tutorial.