Token Handler Development Setup

Token Handler Development Setup

On this page

The token handler pattern follows security best practices for Single Page Applications (SPA). OpenID Connect is combined with an application cookie layer, and only the most secure SameSite=strict cookies are used in the browser. This is done without compromising the web architecture, and Curity also provides open source components, so that you do not need to write any security code.

The separation of concerns used in the token handler pattern does however add complexity to the development setup. In older website architectures, developers ran a web backend that managed static content, the OAuth client, and API routes together:

Web Concerns

In the token handler pattern, the same concerns remain, but deployment is more separated. When adopting the token handler pattern, you need to start with some deployment work. This article describes some cloud native patterns you can use. Once complete, you will be able to write code in a pure SPA manner, while also running production level security on your workstation.

Docker Token Handler

The main components used are introduced in the token handler overview. On a development computer, the simplest way to get started running them is to use docker. A local API gateway then sits in front of the OAuth agent and manages forwarding JWT access tokens to APIs:

Docker Setup

First, run a couple of docker build commands, to configure the gateway and build the OAuth agent. If using Curity open source components, you would run commands similar to these:

bash
12
docker build -t oauthagent:1.0.0 .
docker build -t custom_kong:3.0.0 .

Then use a docker based deployment to run the token handler components locally. A docker compose setup would use settings such as these:

yaml
123456789101112131415161718192021222324252627282930313233343536373839
version: '3.8'
services:
kong-api-gateway:
image: custom_kong:3.0.0
hostname: apigateway-internal
ports:
- 80:3000
volumes:
- ./components/api-gateway/kong/kong.yml:/usr/local/kong/declarative/kong.yml
environment:
KONG_DATABASE: 'off'
KONG_DECLARATIVE_CONFIG: '/usr/local/kong/declarative/kong.yml'
KONG_PROXY_LISTEN: '0.0.0.0:3001'
KONG_LOG_LEVEL: 'info'
KONG_PLUGINS: 'bundled,oauth-proxy,phantom-token'
KONG_NGINX_HTTP_LUA_SHARED_DICT: 'phantom-token 10m'
oauth-agent:
image: oauthagent:1.0.0
hostname: oauthagent-internal
environment:
PORT: 3001
TRUSTED_WEB_ORIGIN: 'http://localhost:3000'
ISSUER: 'https://login.example-dev.com/oauth/v2/oauth-anonymous'
AUTHORIZE_ENDPOINT: 'https://login.example-dev.com/oauth/v2/oauth-authorize'
TOKEN_ENDPOINT: 'https://login.example-dev.com/oauth/v2/oauth-token'
USERINFO_ENDPOINT: 'https://login.example-dev.com/oauth/v2/oauth-userinfo'
LOGOUT_ENDPOINT: 'https://login.example-dev.com/oauth/v2/oauth-session/logout'
CLIENT_ID: 'spa-client'
CLIENT_SECRET: 'Password1'
REDIRECT_URI: 'http://localhost:3000/'
POST_LOGOUT_REDIRECT_URI: 'http://localhost:3000/'
SCOPE: 'openid profile'
COOKIE_DOMAIN: 'localhost'
COOKIE_NAME_PREFIX: 'example'
COOKIE_ENCRYPTION_KEY: 'fda91643fce9af565bdc34cd965b48da75d1f5bd8846bf0910dd6d7b10f06dfe'
CORS_ENABLED: 'true'
SERVER_CERT_P12_PATH: ''
SERVER_CERT_P12_PASSWORD: ''

In this example, static web content is downloaded to the browser from an SPA based web host, such as the webpack development server, running at http://localhost:3000. Meanwhile, the SPA sends OAuth and API requests via the API gateway, which runs at http://localhost:3001.

Same Site Development Setup

Although you can use localhost URLs, it is instead recommended to switch to a more meaningful web origin for development. The simplest option is to invent a web domain name for local development, then add it to your computer's hosts file:

text
1
127.0.0.1 www.example-local.com

The OAuth agent configuration can then be updated to use local development URLs that convey architectural meaning:

yaml
12345678910111213
oauth-agent:
image: oauthagent:1.0.0
hostname: oauthagent-internal
environment:
PORT: 3001
TRUSTED_WEB_ORIGIN: 'http://www.example-local.com:3000'
CLIENT_ID: 'spa-client'
CLIENT_SECRET: 'Password1'
REDIRECT_URI: 'http://www.example-local.com:3000/'
POST_LOGOUT_REDIRECT_URI: 'http://www.example-local.com:3000/'
SCOPE: 'openid profile'
COOKIE_DOMAIN: 'www.example-local.com'
COOKIE_NAME_PREFIX: 'example'

This development setup mirrors the following production deployment, where all three web concerns are hosted together behind a gateway. The only difference during web development is that static content is served locally:

Samesite Deployment

Multi Site Development Setup

Alternatively, you can add two local domains, to separate the web and API concerns more completely:

text
1
127.0.0.1 www.example-local.com api.example-local.com

The OAuth agent configuration can then be updated to use a different cookie domain. Cookies are only needed to securely access APIs, and not during requests for static content:

yaml
12345678910111213
oauth-agent:
image: oauthagent:1.0.0
hostname: oauthagent-internal
environment:
PORT: 3001
TRUSTED_WEB_ORIGIN: 'http://www.example-local.com:3000'
CLIENT_ID: 'spa-client'
CLIENT_SECRET: 'Password1'
REDIRECT_URI: 'http://www.example-local.com:3000/'
POST_LOGOUT_REDIRECT_URI: 'http://www.example-local.com:3000/'
SCOPE: 'openid profile'
COOKIE_DOMAIN: 'api.example-local.com'
COOKIE_NAME_PREFIX: 'example'

This development setup mirrors the following production deployment, where web static content is downloaded from a content delivery network. Of course, during web development, the static content will continue to be served locally:

Multisite Deployment

Develop with a Remote Token Handler

Once deployment is understood, you can then use a DNS solution to avoid needing to run token handler components locally. To do so, run the same Docker deployment to push them to a shared environment, dedicated to enabling productive web development.

One option is to spin up a domain in your cloud platform, while using adding an entry for the web domain to your hosts file. Either HTTP or HTTPS URLs could be used for a development setup. You then have a pure SPA web development setup, and you also operate with the same security as your customer users:

Dev Token Handler

Cross Origin Requests

All of the development setups shown above serve static content from one process, while managing OAuth and API requests from other processes. This requires different ports or host names to be used during development. Requests from the SPA to the API gateway and OAuth agent are therefore cross origin. When calling APIs, the SPA will use CORS request and response headers. The Curity open source components manage this in the correct way.

Full Stack Setups

A Docker based deployment can remain useful, for best troubleshooting. It also provides finer control over custom individual setups, without impacting the rest of the web development team. A common use case is for a developer to want to run both the local SPA and one or more APIs locally. To do so, that developer can simply route from the Docker API gateway back to the local computer, using the well known host name of host.docker.internal:

Full Stack Deployment

Deployment Example

If you are new to the token handler pattern, Curity provides an end-to-end example that you can run on a development computer. The deployment details are also explained, in a separate code example whose settings you can quickly reverse engineer, then apply to your own deployments:

Conclusion

The token handler pattern enables you to implement a secure cookie layer at the application level, while also following a pure SPA development model. This is done by separating web and API concerns, which in turn adds more moving parts to the development setup. Some initial investment in deployment is required, after which you can continue to focus only on web customer experiences, with the difficult security externalized.