/images/resources/tutorials/writing-clients/spa/tutorials_token_handler_deployment.jpg

Token Handler Deployment Example

On this page

The SPA using Token Handler Pattern code example showed how to run an API-driven OpenID Connect flow for a single page application (SPA) on a development computer. This tutorial provides a worked example for the Cloud Native Use Case from Token Handler Deployment Patterns. By following this tutorial you will gain a better understanding of how to deliver utility API components to your deployed environments.

Cloud Native Components

The OAuth for Web page summarizes the open source standards-based components provided by Curity. The cloud native components from this deployment example undergo detailed testing, so you only need to deploy them.

OAuth Agent

These OAuth agent components are provided, and they do the same job as a website OpenID Connect framework. The key difference is that the flow is API-driven, to avoid any adverse impact on SPA development or deployment. Choose this utility API based on your technology preference:

API Gateway Plugins

The SPA calls APIs via a high performance API gateway, which runs plugins. The OAuth proxy plugin manages web security, by decrypting cookies, implementing web security checks, then forwarding an access token to APIs:

If you are using opaque tokens with the Curity Identity Server, the phantom token plugin will also be used, to translate opaque access tokens to JWT access tokens before the API is called:

Using these plugins simplifies your API code, which no longer needs to deal with any web security concerns. The end-to-end API flow can then execute as follows:

API Flow

Code Example Deployment Interface

The SPA Code Example provides basic instructions on deploying the SPA and token handler components. The scripts used support two command line parameters, the first of which is the OAuth agent. Choose this according to your API technology preference:

OAuth Agent TypeDescription
NODEAn OAuth agent implemented in Node.js and using the Express HTTP server
NETAn OAuth Agent implemented in C# and .NET
KOTLINAn OAuth Agent implemented in Spring, using a Java runtime

The second parameter is the type of API gateway:

API Gateway TypeDescription
NGINXThe NGINX HTTP server
OPENRESTYThe OpenResty HTTP server
KONGThe Kong API gateway

Run the deployment with commands similar to the following. This will spin up a number of containers within a small docker network:

bash
12
./build.sh NET KONG
./deploy.sh NET KONG

Later, when you are finished with the deployed system, free all docker resources with the following command:

bash
1
./teardown.sh

Deployed System

When the example SPA is run, the following overall flow of cookies and tokens is used:

SPA Flow

By default the following base URLs are used, with token handler components deployed to an API domain:

ComponentURL
Web Hosthttps://www.example.com
OAuth Agenthttps://api.example.com/oauth-agent
OAuth Proxyhttps://api.example.com/api
Identity Serverhttps://login.example.com:8443

Example Deployment Logic

The detailed token handler deployment work is externalized to the child scripts in this tutorial's spa-deployments repository. Clone the repo using the link at the top of this page and view resources:

Deployment Repo

The main deployment implementation is in these scripts at the root level. Resources can be studied and reverse engineered, when first becoming familiar with token handler components:

ScriptResponsibility
build.shBuilds token handler components into Docker containers
deploy.shConfigures and deploys all components in the end-to-end code example

Run parent scripts at least once

The parent scripts must be run at least once, so that the application level docker containers are built. If you want to study deployment in more depth, or troubleshoot a failed deployment, you can then re-run only the child scripts.

Environment Variables

The following values can be set as environment variables before running the deployment script. Doing so enables you to customize deployed URLs for demo purposes. These are the default values:

bash
1234
export BASE_DOMAIN='example.com'
export WEB_SUBDOMAIN='www'
export API_SUBDOMAIN='api'
export IDSVR_SUBDOMAIN='login'

Same Site Deployments

If you prefer, edit the deployment so that token handler components are deployed to the web domain. This is a common form of backend for frontend (BFF) deployment, which prevents CORS pre-flight requests being issued by the browser:

bash
1234
export BASE_DOMAIN='example.com'
export WEB_SUBDOMAIN='www'
export API_SUBDOMAIN='www'
export IDSVR_SUBDOMAIN='login'

Custom Domains

Another option is to change domains to suit your own product names or brands. If required, add the custom domain to the system's hosts file:

bash
1234
export BASE_DOMAIN='myapp.com'
export WEB_SUBDOMAIN=''
export API_SUBDOMAIN='api'
export IDSVR_SUBDOMAIN='login'

You can then browse to a working example SPA that uses your custom URLs:

SPA with Custom URL

Point to an External Identity Server

You can also point the example deployment to an external identity server, e.g., that of a test environment, rather than deploying an instance of the Curity Identity Server. To do so, configure the following additional environment variable:

bash
1
export EXTERNAL_IDSVR_ISSUER_URI=http://idsvr.mycompany.com:8443/oauth/v2/oauth-anonymous

If the Docker deployment is done on a shared server that exposes web and API domains, this will enable you to publish the demo app to your team or other stakeholders, who can then all sign into the app using your company's own user accounts, e.g, to review the login user experience.

To ensure that the end-to-end solution still works when using an external identity server, you need to create OAuth clients for the SPA, and the introspection client used by the phantom token plugin. If using the Curity Identity Server, import the following XML, updated with your preferred settings:

xml
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
<config xmlns="http://tail-f.com/ns/config/1.0">
<profiles xmlns="https://curity.se/ns/conf/base">
<profile>
<id>token-service</id>
<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>spa-client</id>
<client-name>spa-client</client-name>
<description>SPA with OAuth Agent</description>
<secret>Password1</secret> <!-- Don't forget to change this -->
<redirect-uris>http://myapp.com/</redirect-uris>
<scope>openid</scope>
<scope>profile</scope>
<user-authentication>
<allowed-post-logout-redirect-uris>http://myapp.com/</allowed-post-logout-redirect-uris>
</user-authentication>
<capabilities>
<code>
</code>
</capabilities>
<use-pairwise-subject-identifiers>
<sector-identifier>spa-client</sector-identifier>
</use-pairwise-subject-identifiers>
<validate-port-on-loopback-interfaces>true</validate-port-on-loopback-interfaces>
</client>
<client>
<id>api-gateway-client</id>
<client-name>api-gateway-client</client-name>
<secret>Password1</secret> <!-- Don't forget to change this -->
<capabilities>
<introspection/>
</capabilities>
<use-pairwise-subject-identifiers>
<sector-identifier>api-gateway-client</sector-identifier>
</use-pairwise-subject-identifiers>
</client>
</config-backed>
</client-store>
</authorization-server>
</settings>
</profile>
</profiles>
</config>

Once the import is complete, you can then run the demo SPA while using your preconfigured identity server. This will enable logins with familiar user accounts and your preferred authentication options and login user experience.

External Identity Server

Configuration and Endpoints

Since the code example deployment is based on the cloud native use case, back end components make HTTP requests to the identity server inside the cluster, and use internal host names:

Internal HTTP Calls

In total the following OAuth endpoints are used, where the first and last of these are invoked by the browser, and must therefore use external URLs. If you have pointed the code example to your own identity server, you may need to make some edits to paths in the deployment script:

Endpoint NameCode Example Default URL
Authorizehttp://login.example.com:8443/oauth/v2/oauth-authorize
Tokenhttp://login-internal.example.com:8443/oauth/v2/oauth-token
Introspecthttp://login-internal.example.com:8443/oauth/v2/oauth-introspect
User Infohttp://login-internal.example.com:8443/oauth/v2/oauth-userinfo
End Sessionhttp://login.example.com:8443/oauth/v2/oauth/v2/oauth-session/logout

OAuth Agent Configuration

To deploy one of the OAuth agent utility APIs to production, environment variables such as the following would be used. The example docker compose deployment uses similar URLs, though uses plain HTTP to reduce infrastructure for developers.

yaml
12345678910111213141516171819202122
oauth-agent:
image: oauthagent:1.0.0
hostname: oauthagent-host
environment:
PORT: 3001
TRUSTED_WEB_ORIGIN: 'https://www.example.com'
ISSUER: 'https://login.example.com/oauth/v2/oauth-anonymous'
AUTHORIZE_ENDPOINT: 'https://login.example.com/oauth/v2/oauth-authorize'
TOKEN_ENDPOINT: 'https://login-internal.example.com/oauth/v2/oauth-token'
USERINFO_ENDPOINT: 'https://login-internal.example.com/oauth/v2/oauth-userinfo'
LOGOUT_ENDPOINT: 'https://login.example.com/oauth/v2/oauth-session/logout'
CLIENT_ID: 'spa-client'
CLIENT_SECRET: 'Password1'
REDIRECT_URI: 'https://www.example.com/'
POST_LOGOUT_REDIRECT_URI: 'https:www.example.com/'
SCOPE: 'openid profile'
COOKIE_DOMAIN: 'api.example.com'
COOKIE_NAME_PREFIX: 'example'
COOKIE_ENCRYPTION_KEY: 'fda91643fce9af565bdc34cd965b48da75d1f5bd8846bf0910dd6d7b10f06dfe'
CORS_ENABLED: 'true'
SERVER_CERT_P12_PATH: '/certs/my.p12'
SERVER_CERT_P12_PASSWORD: 'Password1'

Deploying the API Gateway

Custom Docker images are built for the selected gateway, in order to install plugins. This section describes the deployment for the Kong API gateway. For NGINX and OpenResty, the example resources provide similar configuration. First, a custom Dockerfile is built, which downloads plugins when it is built:

dockerfile
1234567
FROM kong:3.0.0-alpine
USER root
RUN luarocks install kong-oauth-proxy 1.3.0 && \
luarocks install kong-phantom-token 2.0.0
USER kong

The gateway and plugins are then deployed, along with API route configuration:

yaml
1234567891011121314
kong_reverse-proxy:
image: custom_kong:3.0.0-alpine
hostname: reverseproxy
ports:
- 80:3000
volumes:
- ./reverse-proxy/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:3000'
KONG_LOG_LEVEL: 'info'
KONG_PLUGINS: 'bundled,oauth-proxy,phantom-token'
KONG_NGINX_HTTP_LUA_SHARED_DICT: 'phantom-token 10m'

API Routes

Routes are specified in a configuration file deployed with the gateway. For Kong they are expressed in the kong.yml file. OAuth agent requests are passed straight through, whereas plugins need to execute when APIs are called, in order to retrieve and forward the JWT access token:

yaml
123456789101112131415161718192021222324252627282930313233343536373839404142
_format_version: '2.1'
_transform: true
services:
- name: webhost
url: http://webhost-internal.example.com:3000
routes:
- name: webhost-api-route
paths:
- /
- name: oauth-agent
url: http://oauthagent-internal.example.com:3001/oauth-agent
routes:
- name: oauth-agent-api-route
paths:
- /oauth-agent
- name: business-api
url: http://api-internal.example.com:3002
routes:
- name: business-api-route
paths:
- /api
plugins:
- name: oauth-proxy
config:
cookie_name_prefix: example
encryption_key: 8329e728ab09a4464a5a01e1edfedce89d8e07a959270cea6e486cc8c844c46e
trusted_web_origins:
- http://www.example.com
cors_enabled: false
- name: phantom-token
config:
introspection_endpoint: http://login-internal.example.com:8443/oauth/v2/oauth-introspect
client_id: api-gateway-client
client_secret: Password1
token_cache_seconds: 900

Cookie Encryption Keys

The repo's deploy.sh script creates a new encryption key on every deployment. This is used with AES256 encryption, to encrypt tokens into cookies before returning them to the browser. The openssl tool is used to create a compliant 32 byte key, which is then configured as 64 hex characters. This hex string is then deployed to both the OAuth agent and the OAuth proxy.

bash
1
ENCRYPTION_KEY=$(openssl rand 32 | xxd -p -c 64)

If the Docker based deployment is re-run when the browser contains a cookie from the previous deployment, the example SPA deals with old cookies in browsers reliably. This is done by handling 401 errors from the OAuth agent or OAuth proxy, then prompting the user to re-authenticate.

Finalize your Deployment

This tutorial has explained the main steps needed to integrate Curity's open source components, when using the token handler pattern to secure your SPAs. It should be solely a deployment task, that only needs doing once. Also ensure that you read the tutorials for the token handler components you have chosen, so that you have an understanding the main behaviors. Finally, ensure that you also do some work to enable a productive Web Development Setup.

Financial Grade Deployment

The Curity open source components use a standard OpenID Connect implementation, with response_type=code, PKCE and a client secret, in line with many website technology stacks. If required, it is possible to extend the default OAuth behavior by forking the OAuth Agent repository and making minor code changes. A more advanced example is provided in the Financial-grade OAuth Agent, which implements these cutting edge standards:

The example deployment can also be run with the financial-grade OAuth agent, using the following commands:

bash
12
./build.sh FINANCIAL NGINX
./deploy.sh FINANCIAL NGINX

The repo then also uses SSL for all components running in the Docker compose network. You can then browse to the SPA at https://www.example.com. Before doing so, locate the development root certificate authority at resources/certs/example.ca.pem and add it to the system trust store, eg the macOS keychain, so that it is trusted by your browser.

Conclusion

The spa-deployments code example is a reference implementation for deploying the moving parts of an SPA that uses the token handler pattern. This tutorial has explained the main deployment logic. In a real company setup, some areas, such as secret management, would need hardening in line with best practices for your platform. Once you have tamed the deployment, you will have a very clean separation of web and API concerns. This will enable your apps to be developed as pure SPAs, while also using the strongest browser security.

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