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:
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 Type | Description |
---|---|
NODE | An OAuth agent implemented in Node.js and using the Express HTTP server |
NET | An OAuth Agent implemented in C# and .NET |
KOTLIN | An OAuth Agent implemented in Spring, using a Java runtime |
The second parameter is the type of API gateway:
API Gateway Type | Description |
---|---|
NGINX | The NGINX HTTP server |
OPENRESTY | The OpenResty HTTP server |
KONG | The 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:
./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:
./teardown.sh
Deployed System
When the example SPA is run, the following overall flow of cookies and tokens is used:
By default the following base URLs are used, with token handler components deployed to an API domain:
Component | URL |
---|---|
Web Host | https://www.example.com |
OAuth Agent | https://api.example.com/oauth-agent |
OAuth Proxy | https://api.example.com/api |
Identity Server | https://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:

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:
Script | Responsibility |
---|---|
build.sh | Builds token handler components into Docker containers |
deploy.sh | Configures 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:
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:
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:
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:

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:
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:
<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.

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:
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 Name | Code Example Default URL |
---|---|
Authorize | http://login.example.com:8443/oauth/v2/oauth-authorize |
Token | http://login-internal.example.com:8443/oauth/v2/oauth-token |
Introspect | http://login-internal.example.com:8443/oauth/v2/oauth-introspect |
User Info | http://login-internal.example.com:8443/oauth/v2/oauth-userinfo |
End Session | http://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.
oauth-agent:image: oauthagent:1.0.0hostname: oauthagent-hostenvironment:PORT: 3001TRUSTED_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:
FROM kong:3.0.0-alpineUSER rootRUN luarocks install kong-oauth-proxy 1.3.0 && \luarocks install kong-phantom-token 2.0.0USER kong
The gateway and plugins are then deployed, along with API route configuration:
kong_reverse-proxy:image: custom_kong:3.0.0-alpinehostname: reverseproxyports:- 80:3000volumes:- ./reverse-proxy/kong.yml:/usr/local/kong/declarative/kong.ymlenvironment: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:
_format_version: '2.1'_transform: trueservices:- name: webhosturl: http://webhost-internal.example.com:3000routes:- name: webhost-api-routepaths:- /- name: oauth-agenturl: http://oauthagent-internal.example.com:3001/oauth-agentroutes:- name: oauth-agent-api-routepaths:- /oauth-agent- name: business-apiurl: http://api-internal.example.com:3002routes:- name: business-api-routepaths:- /apiplugins:- name: oauth-proxyconfig:cookie_name_prefix: exampleencryption_key: 8329e728ab09a4464a5a01e1edfedce89d8e07a959270cea6e486cc8c844c46etrusted_web_origins:- http://www.example.comcors_enabled: false- name: phantom-tokenconfig:introspection_endpoint: http://login-internal.example.com:8443/oauth/v2/oauth-introspectclient_id: api-gateway-clientclient_secret: Password1token_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.
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:
- Mutual TLS Client Authentication (MTLS)
- Pushed Authorization Requests (PAR)
- JWT Secured Authorization Response Mode (JARM)
The example deployment can also be run with the financial-grade OAuth agent, using the following commands:
./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