---
title: Build advanced token security in a JavaScript single-page app
description: This tutorial covers the advanced development required for implementing the Token Vault with the Ping (ForgeRock) SDK for JavaScript.
component: sdks
version: latest
page_id: sdks:token-vault:tutorial/advanced-token-security
canonical_url: https://docs.pingidentity.com/sdks/latest/token-vault/tutorial/advanced-token-security.html
section_ids:
  first_why_advanced_token_security: First, why advanced token security?
  bff: The Backend for Frontend (BFF) pattern
  origin-isolation: Origin Isolation
  what_is_token_vault: What is Token Vault?
  what_you_will_learn: What you will learn
  using_this_tutorial: Using this tutorial
  requirements: Requirements
  knowledge_requirements: Knowledge requirements
  technical_requirements: Technical requirements
  authorization_server_setup: Authorization server setup
  CORS: Step 1. Configure CORS (Cross-Origin Resource Sharing)
  step_2_create_two_oauth_2_0_clients: Step 2. Create two OAuth 2.0 clients
  public_oauth_2_0_client_settings: Public OAuth 2.0 client settings
  confidential_oauth_2_0_client_settings: Confidential OAuth 2.0 client settings
  step_3_create_a_test_user: Step 3. Create a test user
  local_project_setup: Local project setup
  step_1_installing_the_project: Step 1. Installing the project
  step_2_create_an_env_file: Step 2. Create an .env file
  build_and_run_the_project: Build and run the project
  open_the_app_in_browser: Open the app in browser
  install_token_vault_module: Install Token Vault module
  code: Implement the Token Vault Proxy
  step_1_scaffold_the_proxy: Step 1. Scaffold the Proxy
  step_2_add_the_npm_workspace: Step 2. Add the npm workspace
  step_3_setup_the_supporting_files: Step 3. Setup the supporting files
  step_4_create_and_configure_the_proxy: Step 4. Create and configure the Proxy
  step_5_build_and_verify_the_proxy: Step 5. Build and verify the Proxy
  implement_the_token_vault_interceptor: Implement the Token Vault Interceptor
  interceptor-config: Step 1. Create the Token Vault Interceptor build config
  step_2_create_the_new_token_vault_interceptor_file: Step 2. Create the new Token Vault Interceptor file
  step_3_import_and_initialize_the_interceptor_module: Step 3. Import and initialize the interceptor module
  step_4_build_the_interceptor: Step 4. Build the Interceptor
  interceptor-js: Step 5. Ensure interceptor.js is accessible
  implement_the_token_vault_client: Implement the Token Vault Client
  step_1_add_html_element_to_index_html: Step 1. Add HTML element to index.html
  step_2_import_and_initialize_the_client_module: Step 2. Import and initialize the client module
  step_2_register_the_interceptor_proxy_and_token_store: Step 2. Register the interceptor, proxy, and token store
  step_3_replace_the_sdks_default_token_store: Step 3. Replace the SDK's default token store
  step_4_check_for_existing_tokens: Step 4. Check for existing tokens
  build_and_run_the_apps: Build and run the apps
  troubleshooting: Troubleshooting
  getting_failures_in_the_service_worker_registration: Getting failures in the service worker registration
  tokens_are_not_being_saved_under_any_origin: Tokens are not being saved under any origin
  im_getting_a_cors_failure: I'm getting a CORS failure
---

# Build advanced token security in a JavaScript single-page app

This tutorial covers the advanced development required for implementing the Token Vault with the Ping (ForgeRock) SDK for JavaScript.

## First, why advanced token security?

In JavaScript Single Page Applications (or SPA), OAuth/OIDC Tokens (referred to from here on as *tokens*) are typically stored by using the browser's [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API): `localStorage` or `sessionStorage`.

The security mechanism the browser uses to ensure data doesn't leak out to unintended actors is through the [Same-Origin Policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy). In short, only JavaScript running on the exact same origin, that is scheme, domain, and port, can access the stored data.

For example, if an SPA running on `https://auth.example.com/login` stores data, JavaScript running on the following ***will*** be able to access it:

* `https://auth.example.com/users`: origins match, regardless of path

* `https://auth.example.com?status=abc`: origins match, regardless of query parameters

The following ***will NOT*** be able to access the data:

* `http://auth.example.com`: uses a different scheme, `http` vs. `https`

* `https://auth.examples.com`: uses a different domain; notice the plurality

* `https://example.com`: does not include the sub-domain

* `https://auth.example.com:8000`: uses a different port

For the majority of web applications, this security model can be sufficient.

In most JavaScript applications, the code running on the app's origin can usually be trusted; hence, the browser's Web Storage API is sufficient as long as good security practices are in place.

However, in applications that are high-value targets, such as apps required to run untrusted, third-party code, or apps that have elevated scrutiny due to regulatory or compliance needs, the Same-Origin Policy may not be enough to protect the stored tokens.

Examples of situations where the Same-Origin Policy may not be sufficient include government agencies, financial organizations, or those that store sensitive data, such as medical records. The web applications of these entities may have enough inherent risk to offset the complexity of a more advanced token security implementation.

There are two solutions that can increase token security:

1. [Backend for Frontend (BFF)](#bff)

2. [Origin Isolation](#origin-isolation)

## The Backend for Frontend (BFF) pattern

One solution that is quite common is to avoid storing high-value secrets within the browser in the first place. This can be done with a dedicated [*Backend for Frontend*](https://samnewman.io/patterns/architectural/bff/), or BFF. Yeah, it's a silly initialism.

This is increasingly becoming a common pattern for apps made with common meta-frameworks, such as Next, Nuxt, SvelteKit, and so on. The server component of these frameworks can store the tokens on the front end's behalf using in-memory stores or just writing the token into an *HTTPOnly* cookie, and requests that require authorization can be proxied through the accompanying server where the tokens are accessible. Therefore, no token needs to be stored on the front end, eliminating the risk.

You can read more about the arguments in favor of, and against, this design in the [Hacker News discussion on the blog post titled "SPAs are Dead"](https://news.ycombinator.com/item?id=26728596).

If, on the other hand, you are not in the position to develop, deploy and maintain a full backend, you have an alternative choice for securing your tokens: [Origin Isolation](#origin-isolation).

## Origin Isolation

Origin Isolation is a concept that introduces an alternative to [BFF](#bff), and provides a more advanced mechanism for token security.

The concept is to store tokens in a *different* and dedicated origin related to the main application. To do this, two web servers are needed to respond to two different origins: one is your main app, and the second is an *iframed* app dedicated to managing tokens.

For more information, refer to the [patent: Transparently using origin isolation to protect access tokens](https://patents.justia.com/patent/11689528).

This particular design means that if your main application gets compromised, the malicious code running in your main app's origin still has no access to the tokens stored in the alternative origin.

You'll still need a web server of some kind to serve the files necessary to handle requests to this alternative origin, but being that only static files are served, the options are much simpler and lightweight.

This solution, which is implemented by using the Token Vault is the focus of this tutorial.

## What is Token Vault?

Token Vault is a codified implementation of Origin Isolation. For more information, refer to [Token Vault](../index.html) in the documentation.

It is a plugin available to customers that use the Ping (ForgeRock) SDK for JavaScript to enable OAuth 2.0 or OIDC token request and management in their apps. These apps can remain largely unmodified to take advantage of Token Vault.

|   |                                                                                                                                                                                                                                                                                                                   |
| - | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | Even though your main app doesn't need much modification, additional build and server requirements are necessary, which introduces complexity and added maintenance to your system.We recommend that you only integrate Token Vault into an app if heightened security measures are a requirement of your system. |

## What you will learn

We will use an existing React JS, to-do sample application similar to what [we built in another guide](../../sdks/tutorials/reactjs/index.html) as a starting point for implementing Token Vault. This represents a realistic web application that has an existing implementation of the Ping (ForgeRock) SDK for JavaScript. Unlike the previous tutorial, we'll start with a fully working app and focus on adding Token Vault.

This tutorial focuses on OAuth 2.0 and OIDC authorization and token management. Authentication-related concerns, such as login and registration journeys are handled outside the app. We call this approach OIDC login.

|   |                                                                                                                                                                                                                                                                                                                                                                                |
| - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|   | This is not a guide on how to build a React appHow you architect or construct React apps is outside the scope of this guide.It's also worth noting that there are many React-based frameworks and generators for building web applications, such as Next.js, Remix, and Gatsby.What is *best* is highly contextual to the product and user requirements for any given project. |

To demonstrate Token Vault integration, we will be using a simplified, non-production-ready, to-do app. This to-do app represents a Single Page Application (SPA) with nothing more than React Router added to the stack for routing and redirection.

## Using this tutorial

This is a *hands-on* tutorial. We are providing the web app and resource server for you. You can find the repo on GitHub to follow along.

All you'll need is your own PingOne Advanced Identity Cloud or PingAM. If you don't have access to either, reach out to a representative today, and we'll be happy to get you started.

There are two ways to use this guide:

1. Follow along by building portions of the app yourself: continue by ensuring you can [meet the requirements](#requirements) below.

2. Just curious about the code implementation details: skip to [Implementing the Token Vault](#code).

## Requirements

### Knowledge requirements

1. JavaScript and the npm ecosystem of modules

2. The command line interface, such as Shell or Bash

3. Core Git commands, including `clone` and `checkout`

4. React and basic React conventions

5. Context API: the concept for managing global state

6. Build systems/bundlers and development servers: We use basic Vite configuration and commands

### Technical requirements

* Admin access to an instance of PingOne Advanced Identity Cloud or self-managed PingAM

* Node.js >= `16`

  Check your version with `node -v`

* npm >= `8`

  Check your version with `npm -v`

## Authorization server setup

|   |                                                                                                                                                                                                                                         |
| - | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | If you've already completed the previous tutorial for React JS or Angular, then you may already have most of this setup within your server. We'll call out the newly introduced data points to ensure you don't miss the configuration. |

### Step 1. Configure CORS (Cross-Origin Resource Sharing)

Due to the fact that pieces of our system will be running on different origins (scheme, domain, and port), we need to configure CORS in the server to allow our web app to make requests. Use the following values:

* **Allowed Origins**: `http://localhost:5173` `http://localhost:5175`

* **Allowed Methods**: `GET` `POST`

* **Allowed headers**: `accept-api-version` `authorization` `content-type` `x-requested-with`

* **Allow credentials**: enable

Rather than domain aliases and self-signed certificates, we will use `localhost` as that is a trusted domain by default. The main application will be on the `5173` port, and the proxy will be on the 5175 port. Because the proxy also make calls to the PingAM server, its origin must also be allowed.

|   |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | Service Workers are not compatible with self-signed certificates, regardless of tool or creation method.Certificates from a system-trusted, valid Certificate Authority (CA) are required or direct use of `localhost`. Self-signed certificates lead to a `fetch` error similar to the following:```text
Failed to register a ServiceWorker for scope ('https://example.com:8000/') with script ('https://example.com:8000/sw.js'):
An unknown error occurred when fetching the script
``` |

![Screenshot of Token Vault CORS configuration with two origins in PingOne Advanced Identity Cloud](../../_images/token-vault/token-vault-cors-config.png)Figure 1. Example CORS configuration in PingOne Advanced Identity Cloud

For more information about CORS configuration, refer to the following:

* [Configure CORS in PingAM](https://docs.pingidentity.com/pingam/8/security-guide/enable-cors-support.html)

* [Configure CORS in PingOne Advanced Identity Cloud](https://docs.pingidentity.com/pingoneaic/latest/tenants/configure-cors.html).

### Step 2. Create two OAuth 2.0 clients

Within the server, create two OAuth 2.0 clients: one for the React web app and one for the Node.js resource server.

Why two? It's conventional to have one OAuth 2.0 client per app in the system. For this case, a public OAuth 2.0 client for the React app provides our app with OAuth 2.0 or OIDC tokens. The Node.js server validates the user's Access Token shared via the React app using its own confidential OAuth 2.0 client.

#### Public OAuth 2.0 client settings

* **Client name/ID**: `CentralLoginOAuthClient`

* **Client type**: `Public`

* **Secret**: `<leave empty>`

* **Scopes**: `openid` `profile` `email`

* **Grant types**: `Authorization Code` `Refresh Token`

* **Implicit consent**: enabled

* **Redirection URLs/Sign-in URLs**: `http://localhost:5173/login`

* Response types: `code` `id_token` `refresh_token`

* **Token authentication endpoint method**: `none`

|   |                                                                                                                                                          |
| - | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | The client name and redirection/sign-in URL values have changed from the previous tutorial, as well as the Refresh Token grant and response type values. |

#### Confidential OAuth 2.0 client settings

* **Client name/ID**: `RestOAuthClient`

* **Client type**: `Confidential`

* **Secret**: `<alphanumeric string>` (treat it like a password)

* **Default scope**: `am-introspect-all-tokens`

* **Grant types**: `Authorization Code`

* **Token authentication endpoint method**: `client_secret_basic`

![Screenshot of a Centralized Login OAuth 2.0 client configuration in PingOne Advanced Identity Cloud](../../_images/token-vault/central-login-oauth-client-config.png)Figure 2. Example OAuth 2.0 client from PingOne Advanced Identity Cloud

Select the environment you are using for more information on configuring OAuth 2.0 clients:

> **Collapse: PingOne Advanced Identity Cloud**
>
> Public clients do not use a client secret to obtain tokens because they are unable to keep them hidden. The Ping (ForgeRock) SDKs commonly use this type of client to obtain tokens, as they cannot guarantee safekeeping of the client credentials in a browser or on a mobile device.
>
> To register a *public* OAuth 2.0 client application for use with the SDKs in PingOne Advanced Identity Cloud, follow these steps:
>
> 1. Log in to your PingOne Advanced Identity Cloud tenant.
>
> 2. In the left panel, click Applications.
>
> 3. Click [icon: plus, set=fa]Custom Application.
>
> 4. Select OIDC - OpenId Connect as the sign-in method, and then click Next.
>
> 5. Select Native / SPA as the application type, and then click Next.
>
> 6. In Name, enter a name for the application, such as `Public SDK Client`.
>
> 7. In Owners, select a user that is responsible for maintaining the application, and then click Next.
>
>    |   |                                                                                    |
>    | - | ---------------------------------------------------------------------------------- |
>    |   | When trying out the SDKs, you could select the `demo` user you created previously. |
>
> 8. In Client ID, enter `sdkPublicClient`, and then click Create Application.
>
>    PingOne Advanced Identity Cloud creates the application and displays the details screen.
>
> 9. On the Sign On tab:
>
>    1. In Sign-In URLs, enter the following values:
>
>       |   |                                                             |
>       | - | ----------------------------------------------------------- |
>       |   | Also add any other domains where you host SDK applications. |
>
>    2. In Grant Types, enter the following values:
>
>       `Authorization Code`
>
>       `Refresh Token`
>
>    3. In Scopes, enter the following values:
>
>       `openid profile email address`
>
> 10. Click Show advanced settings, and on the Authentication tab:
>
>     1. In Token Endpoint Authentication Method, select `none`.
>
>     2. In Client Type, select `Public`.
>
>     3. Enable the Implied Consent property.
>
> 11. Click Save.
>
> The application is now configured to accept client connections from and issue OAuth 2.0 tokens to the example applications and tutorials covered by this documentation.

> **Collapse: Self-managed PingAM server**
>
> The provider specifies the supported OAuth 2.0 configuration options for a realm.
>
> To ensure the PingAM OAuth 2.0 provider service is configured for use with the Ping (ForgeRock) SDKs, follow these steps:
>
> 1. Log in to the PingAM admin UI as an administrator.
>
> 2. In the left panel, click [icon: plug, set=fa]Services.
>
> 3. In the list of services, click OAuth2 Provider.
>
> 4. On the Core tab, ensure Issue Refresh Tokens is enabled.
>
> 5. On the Consent tab, ensure Allow Clients to Skip Consent is enabled.
>
> 6. Click Save Changes.

### Step 3. Create a test user

Create a test user, or *identity*, in your server within the realm you will be using.

Select the environment you are using for instructions on how to create a demo user:

> **Collapse: PingOne Advanced Identity Cloud tenant**
>
> The samples and tutorials in this documentation often require that you have an identity set up so that you can test authentication.
>
> To create a demo user in PingOne Advanced Identity Cloud, follow these steps:
>
> 1. Log in to your PingOne Advanced Identity Cloud tenant.
>
> 2. In the left panel, click Identities > Manage.
>
> 3. Click [icon: plus, set=fa]New Alpha realm - User.
>
> 4. Enter the following details:
>
>    * **Username** = `demo`
>
>    * **First Name** = `Demo`
>
>    * **Last Name** = `User`
>
>    * **Email Address** = `demo.user@example.com`
>
>    * **Password** = `Ch4ng3it!`
>
> 5. Click Save.

> **Collapse: Self-managed PingAM server**
>
> The samples and tutorials in this documentation often require that you have an identity set up so that you can test authentication.
>
> To create a demo user in PingAM, follow these steps:
>
> 1. Log in to the PingAM admin UI as an administrator.
>
> 2. Navigate to [icon: address-card, set=fa]Identities, and then click [icon: plus, set=fa]Add Identity.
>
> 3. Enter the following details:
>
>    * **User ID** = `demo`
>
>    * **Password** = `Ch4ng3it!`
>
>    * **Email Address** = `demo.user@example.com`
>
> 4. Click Create.

## Local project setup

### Step 1. Installing the project

First, clone the [forgerock-sample-web-react-ts repo](https://github.com/cerebrl/forgerock-sample-web-react-ts) to your local computer, `cd` (change directory) into the project folder, check out the branch for this guide, and install the needed dependencies:

```shell
git clone https://github.com/cerebrl/forgerock-sample-web-react-ts
cd forgerock-sample-web-react-ts
git checkout blog/token-vault-tutorial/start
npm install
```

|   |                                                                                                                                                                      |
| - | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | There's also a branch that represents the completion of this guide. If you get stuck, you can check out the `blog/token-vault-tutorial/complete` branch from GitHub. |

### Step 2. Create an `.env` file

First, open the `.env.example` file in the root directory. Copy this file and rename it `.env`. Add your relevant values to this new file as it provides all the important configuration settings to your applications.

Here's a *hypothetical* example `.env` file:

Example of a populated `.env` file

```properties
# .env

# System settings
VITE_AM_URL=https://openam-forgerock-sdks.forgeblocks.com/am/ # Needs to be your {fr_server}
VITE_APP_URL=http://localhost:5173
VITE_API_URL=http://localhost:5174
VITE_PROXY_URL=http://localhost:5175 # This will be our Token Vault Proxy URL

# {am_name} settings
VITE_AM_JOURNEY_LOGIN=Login # Not used with Centralized Login
VITE_AM_JOURNEY_REGISTER=Registration # Not used with Centralized Login
VITE_AM_TIMEOUT=50000
VITE_AM_REALM_PATH=alpha
VITE_AM_WEB_OAUTH_CLIENT=CentralLoginOAuthClient
VITE_AM_WEB_OAUTH_SCOPE=openid email profile

# {am_name} settings for your API (todos) server
# (does not need VITE prefix)
AM_REST_OAUTH_CLIENT=RestOAuthClient
AM_REST_OAUTH_SECRET=6MWE8hs46k68g9s7fHOJd2LEfv # Don't use; this is just an example
```

We are using Vite for our client apps' build system and development server, so by using the `VITE_` prefix, Vite automatically includes these environment variables in our source code. Here are descriptions for some of the values:

* `VITE_AM_URL`: This should be your server and the URL almost always ends with `/am/`

* `VITE_APP_URL`, `VITE_API_URL`, and `VITE_PROXY_URL`: These will be the URLs you use for your locally running apps

* `VITE_AM_REALM_PATH`: The realm of your server. Likely, `alpha` if using PingOne Advanced Identity Cloud or `root` if using a self-managed PingAM server

* `VITE_REST_OAUTH_CLIENT` and `VITE_REST_OAUTH_SECRET`: This is the OAuth 2.0 client you configure in your server to support the REST API server

## Build and run the project

Now that everything is set up, build and run the to-do app project. Open two terminal windows and use the following commands in the root directory of the SDK repo:

First terminal window

```shell
# Start the React app
npm run dev:react
```

This `dev:react` command uses Vite restarts on any change to a dependent file. This also applies to the `dev:proxy` we will build shortly.

Second terminal window

```shell
# Start the Rest API
npm run dev:api
```

This `dev:api` command runs a basic Node server with no "watchers." This should not be relevant as you won't have to modify any of its code. If a change is made within the `todo-api-server` workspace or an environment variable it relies on, a restart would be required.

|   |                                                                                                                           |
| - | ------------------------------------------------------------------------------------------------------------------------- |
|   | We use npm workspaces to manage our multiple sample apps but understanding how it works is not relevant to this tutorial. |

## Open the app in browser

In a *different* browser than the one you are using to administer the server, visit the following URL: `http://localhost:5173`. For example, you could use Edge for the app development and Chrome for the server administration.

A home page should be rendered explaining the purpose of the project. It should look like the example below, but it might be a dark variant if you have the dark theme/mode set in your OS:

![Screenshot of the home page of the sample app (logged out experience)](../../_images/token-vault/demo-app-home-page.png)Figure 3. To-do app home page

If you encounter errors, here are a few tips:

* Visit `http://localhost:5174/healthcheck` in the same browser you use for the React app; ensure it responds with "OK"

* Check the terminal that has the `dev:react` command running for error output

* Ensure you are not logged into the server within the same browser as the sample app; log out if you are and use a different browser

Click the Sign in link in the header or in the Getting started section to sign in to the app with your test user. After successfully authenticating, you should see the app respond to the existence of the valid tokens.

Open your browser's developer tools to inspect its `localStorage`. You should see a single origin with an object containing tokens:

![Screenshot of the home page of the sample app with a logged-in user, showing tokens in Local Storage](../../_images/token-vault/demo-app-home-page-logged-in.png)Figure 4. To-do app home page when logged in as a user

## Install Token Vault module

Install the Token Vault npm module within the root of the app:

```sh
npm install @forgerock/token-vault
```

This npm module is used throughout multiple applications in our project, so installing it at the root rather than at the app or workspace level is a benefit.

## Implement the Token Vault Proxy

### Step 1. Scaffold the Proxy

Next, we'll need to create a third application, the Token Vault Proxy.

Follow this structure when creating the new directory and its files:

```diff
  root
  ├── todo-api-server/
  ├── todo-react-app/
+ └─┬ token-vault-proxy/
+   ├─┬ src/
+   │ └── index.ts
+   ├── index.html
+   ├── package.json
+   └── vite.config.ts
```

### Step 2. Add the npm workspace

To ease some of the dependency management and script running, add this new "workspace" to our root `package.json`, and then add a new script to our `scripts`:

Root `package.json` file

```diff
@@ package.json @@

@@ collapsed @@

  "scripts": {
    "clean": "git clean -fdX -e \"!.env\"",
    "build:api": "npm run build --workspace todo-api-server",
    "build:react": "npm run build --workspace todo-react-app",
+   "build:proxy": "npm run build --workspace token-vault-proxy",
    "dev:api": "npm run dev --workspace todo-api-server",
    "dev:react": "npm run dev --workspace todo-react-app",
+   "dev:proxy": "npm run dev --workspace token-vault-proxy",
    "dev:server": "npm run dev --workspace todo-api-server",
    "lint": "eslint --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "serve:api": "npm run serve --workspace todo-api-server",
-   "serve:react": "npm run serve --workspace todo-react-app"
+   "serve:react": "npm run serve --workspace todo-react-app",
+   "serve:proxy": "npm run serve --workspace token-vault-proxy"
  },

@@ collapsed @@

  "workspaces": [
    "todo-api-server"
-   "todo-react-app"
+   "todo-react-app",
+   "token-vault-proxy",
  ]
```

### Step 3. Setup the supporting files

Create a new directory at the root named `token-vault-proxy`, then create a `package.json` file:

Token Vault Proxy `package.json` file

```diff
@@ token-vault-proxy/package.json @@

+ {
+   "name": "token-vault-proxy",
+   "private": true,
+   "version": "1.0.0",
+   "description": "The proxy for Token Vault",
+   "main": "index.js",
+   "scripts": {
+     "dev": "vite",
+     "build": "vite build",
+     "serve": "vite preview --port 5175"
+   },
+   "dependencies": {},
+   "devDependencies": {},
+   "license": "MIT"
+ }
```

Now, create the Vite config file:

Token Vault Proxy Vite configuration file

```diff
@@ token-vault-proxy/vite.config.ts @@

+ import { defineConfig, loadEnv } from 'vite'; (1)
+
+ // https://vitejs.dev/config/
+ export default defineConfig(({ mode }) => { (2)
+   const env = loadEnv(mode, `${process.cwd()}/../`); (3)
+   const port = Number(new URL(env.VITE_APP_URL).port); (4)
+
+   return { (5)
+     envDir: '../', // Points to the `.env` created in the root dir
+     root: process.cwd(),
+     server: {
+       port,
+       strictPort: true,
+     },
+   };
+ });
```

What does the above do? Good question! Let's review it:

|       |                                                                                                                          |
| ----- | ------------------------------------------------------------------------------------------------------------------------ |
| **1** | We import helper functions from `vite`                                                                                   |
| **2** | Using `defineConfig`, we pass in a function, as opposed to an object, because we want to calculate values at runtime     |
| **3** | The parameter `mode` helps inform Vite how the config is being executed, useful when you need to calculate env variables |
| **4** | Then, extract the `port` out of our app's configured origin, which *should* be `5175`                                    |
| **5** | Finally, use this data to construct the config object and return it                                                      |

Now, create the `index.html` file. This file can be overly simple as all you need is the inclusion of the JavaScript file that will be our proxy:

Token Vault Proxy `index.html` file

```diff
@@ token-vault-proxy/index.html @@

+ <!DOCTYPE html>
+ <html>
+   <p>Proxy is OK</p>
+   <script type="module" src="src/index.ts"></script>
+ </html>
```

If you're not familiar with how Vite works, seeing the `.ts` extension may look a bit odd in an HTML file but don't worry. Vite uses this to find entry files, and it rewrites the actual `.js` reference for us.

### Step 4. Create and configure the Proxy

Let's create and configure the Token Vault Proxy according to our needs. First, create the `src` directory and the `index.ts` file within it.

Token Vault Proxy `index.ts` file

```diff
@@ src/index.ts @@

+ import { proxy } from '@forgerock/token-vault';
+
+ // Initialize the token vault proxy
+ proxy({
+   app: {
+     origin: new URL(import.meta.env.VITE_APP_URL).origin, (1)
+   },
+   forgerock: { (2)
+     clientId: import.meta.env.VITE_AM_WEB_OAUTH_CLIENT,
+     scope: import.meta.env.VITE_AM_WEB_OAUTH_SCOPE,
+     serverConfig: {
+       baseUrl: import.meta.env.VITE_AM_URL,
+     },
+     realmPath: import.meta.env.VITE_AM_REALM_PATH,
+   },
+   proxy: { (3)
+     urls: [`${import.meta.env.VITE_API_URL}/*`],
+   }
+ });
```

The configuration above represents the minimum needed to create the Token Vault Proxy:

|       |                                                                                                                                         |
| ----- | --------------------------------------------------------------------------------------------------------------------------------------- |
| **1** | We need to declare the app's origin, as that's the only source to which the Proxy will respond.                                         |
| **2** | We have the configuration in order for the Proxy to call out to the server effectively for token lifecycle management.                  |
| **3** | Finally, there's the Proxy's `urls` array that acts as an allow-list to ensure only valid URLs are proxied with the appropriate tokens. |

### Step 5. Build and verify the Proxy

With everything set up, build the proxy app and verify it's being served correctly.

```sh
npm run dev:proxy
```

Once the script finishes its initial build and runs the server, you can now check the app and ensure it's running. Go to `http://localhost:5175` in your browser. You should see "Proxy is OK" printed on the screen, and there should be no errors in the Console or Network tab of your browser's dev tools.

![Screenshot of the Proxy directly viewed in browser with text 'Proxy is OK'](../../_images/token-vault/viewing-proxy-in-browser.png)Figure 5. Proxy viewed directly in browser

## Implement the Token Vault Interceptor

### Step 1. Create the Token Vault Interceptor build config

Since the Token Vault Interceptor is a Service Worker, it needs to be bundled separately from your main application code. To do this, write a *new* Vite config file within the `todo-react-app` directory/workspace named `vite.interceptor.config.ts`.

We do not recommend trying to use the same configuration file for both your app and Interceptor.

```diff
  root
  ├── ...
  ├─┬ todo-react-app/
  │ ├── ...
  │ ├── vite.config.ts
+ │ └── vite.interceptor.config.ts
```

Now that you have the new Vite config for the Interceptor, import the `defineConfig` method and pass it the appropriate configuration.

Token Vault Interceptor `vite.interceptor.config.ts` file

```diff
@@ todo-react-app/vite.interceptor.config.ts @@

+ import { defineConfig } from 'vite';
+
+ // https://vitejs.dev/config/
+ export default defineConfig({
+   build: {
+     emptyOutDir: false,
+     rollupOptions: {
+       input: 'src/interceptor/index.ts',
+       output: {
+         dir: 'public', // Treating this like a static asset is important
+         entryFileNames: 'interceptor.js',
+         format: 'iife', // Very important for better browser support
+       },
+     },
+   },
+   envDir: '../', // Points to the `.env` created in the root dir
+ });
```

|   |                                                                                                                                                                   |
| - | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | Rather than passing a function into `defineConfig`, we are passing a plain config object.This is because we don't need any variables at runtime, like env values. |

In the above, we provide the Token Vault Interceptor source file as the input, and then explicitly tell Vite to bundle it as an IIFE ([Immediately Invoked Function Expression](https://developer.mozilla.org/en-US/docs/Glossary/IIFE)) and save the output to this app's `public` directory. This means the Token Vault Interceptor will be available as a static asset at the root of our web server.

It is important to know that bundling it as an IIFE and configuring the output to the public directory is intentional and important. Bundling as an IIFE removes any module system from the file, which is vital to supporting all major browsers within the Service Worker context. Outputting it to the public directory like a static asset is also important. It allows the `scope` of the Service Worker to also be available at the root.

For more information, refer to [Service Worker scopes on MDN](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register).

### Step 2. Create the new Token Vault Interceptor file

Let's create the new Token Vault Interceptor source file that is expected as the entry file to our new Vite config.

```diff
  root
  ├─┬ todo-react-app
  │ ├─┬ src/
+ │ │ ├─┬ interceptor/
+ │ │ │ └── index.ts
  │ │ └── ...
  │ ├── ...
  │ └── vite.interceptor.config.ts
```

### Step 3. Import and initialize the `interceptor` module

Configure your Token Vault Interceptor with the following variables from your `.env` file.

Token Vault Interceptor `index.ts` source file

```diff
@@ todo-react-app/src/interceptor/index.ts @@

+ import { interceptor } from "@forgerock/token-vault";
+
+ interceptor({
+   interceptor: {
+     urls: [`${import.meta.env.VITE_API_URL}/*`], (1)
+   },
+   forgerock: { (2)
+     serverConfig: {
+       baseUrl: import.meta.env.VITE_AM_URL,
+       timeout: import.meta.env.VITE_AM_TIMEOUT,
+     },
+     realmPath: import.meta.env.VITE_AM_REALM_PATH,
+   },
+ });
```

The above only covers the minimal configuration needed, but it's enough to get a basic Interceptor started.

|       |                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **1** | The `urls` array represents all the URLs you'd like intercepted and proxied through the Token Vault Proxy in order for the Access Token to be added to the outbound request. This should only be for requesting your "protected resources."The wildcard (`*`) can be used if you want a catch-all for endpoints of a certain origin or root path. Full glob patterns are *not* supported, so a URL value can only end with `*`. |
| **2** | The configuration here must match the configuration in the main app. This is easily enforced by using the `.env` file                                                                                                                                                                                                                                                                                                           |

### Step 4. Build the Interceptor

Now that we have the dedicated Vite config and the Token Vault Interceptor entry file created, add a dedicated build command to the `package.json` within the `todo-react-app` workspace.

React app `package.json` file

```diff
@@ todo-react-app/package.json @@

@@ collapsed @@

    "scripts": {
-     "dev": "vite",
+     "dev": "npm run build:interceptor && vite",
-     "build": "vite build",
+     "build": "npm run build:interceptor && vite build",
+     "build:interceptor": "vite build -c ./vite.interceptor.config.ts",
      "serve": "vite preview --port 5173"
    },

@@ collapsed @@
```

It's worth noting that the Token Vault Interceptor will only be rebuilt at the start of the command and not rebuilt after any change thereafter as there's no `watch` command used here for the Token Vault Interceptor itself. Once this portion of code is correctly set up, it should rarely change, so this should be fine.

Your main app will still be rebuilt and "hot-reloading" will take place.

Enter `npm run build:interceptor -w todo-react-app` to run the new command you just wrote above in the `todo-react-app` workspace. You can see the resulting `interceptor.js` built and placed into your `public` directory.

```diff
  root
  ├─┬ todo-react-app
  │ ├─┬ public/
  │ │ ├── ...
+ │ │ ├── interceptor.js
```

### Step 5. Ensure `interceptor.js` is accessible

Since we haven't implemented the Token Vault Interceptor yet in the main app, we can't really test it; however, we can at least make sure the file is accessible in the browser as we expect. To do this, run the following command:

```sh
npm run dev:react
```

After the command starts the server, using your browser, visit `http://localhost:5173/interceptor.js`.

You should plainly see the fully built JavaScript file. Ensure it does not have any `import` statements and looks complete. It should contain more code than just the original source file you wrote above.

![Viewing raw JavaScript from Interceptor file in a browser](../../_images/token-vault/interceptor-file-raw.png)Figure 6. Raw JavaScript of Interceptor

## Implement the Token Vault Client

Now that we have all the separate pieces set up, wire it all together with the Token Vault Client plugin.

### Step 1. Add HTML element to `index.html`

When we initiate the Token Vault Proxy, it needs a real DOM element to mount to. The easiest way to ensure we have a proper element is to add it to the `index.html` directly.

React app `index.html` file

```diff
@@ todo-react-app/index.html @@

@@ collapsed @@

    <body>
      <!-- Root div for mounting React app -->
      <div id="root" class="cstm_root"></div>
+
+     <!-- Root div for mounting Token Vault Proxy (iframe) -->
+     <div id="token-vault"></div>

      <!-- Import React app -->
      <script type="module" src="/src/index.tsx"></script>
    </body>
  </html>
```

### Step 2. Import and initialize the `client` module

First, import the `client` module and remove the `TokenStorage` module from the SDK import.

Second, call the `client` function with the below minimal configuration. This is how we "glue" the three entities together within your main app. This function returns an object that we use to register and instantiate each entity.

React app `index.tsx` source file

```diff
@@ todo-react-app/src/index.tsx @@

- import { Config, TokenStorage } from '@forgerock/javascript-sdk';
+ import { Config } from '@forgerock/javascript-sdk';
+ import { client } from '@forgerock/token-vault';
  import ReactDOM from 'react-dom/client';

@@ collapsed @@

+ const register = client({
+   app: {
+     origin: c.TOKEN_VAULT_APP_ORIGIN,
+   },
+   interceptor: {
+     file: '/interceptor.js', // references public/interceptor.js
+   },
+   proxy: {
+     origin: c.TOKEN_VAULT_PROXY_ORIGIN,
+   },
+ });

/**
   * Initialize the React application
   */
  (async function initAndHydrate() {

@@ collapsed @@
```

Remember, the `file` reference within the `interceptor` object needs to point to the *built* Token Vault Interceptor file, which will be located in the `public` directory as a static file but served from the root, not the *source file* itself.

This function ensures the app, Token Vault Interceptor and Token Vault Proxy are appropriately configured.

### Step 2. Register the interceptor, proxy, and token store

Now that we've initialized and configured the client, we now register the Token Vault Interceptor, the Token Vault Proxy, and the token vault store just under the newly added code from above:

React app `index.tsx` source file

```diff
@@ todo-react-app/src/index.tsx @@

@@ collapsed @@

   proxy: {
      origin: c.TOKEN_VAULT_PROXY_ORIGIN,
    },
  });
+
+ // Register the Token Vault Interceptor
+ await register.interceptor();
+
+ // Register the Token Vault Proxy
+ await register.proxy(
+   // This must be a live DOM element; it cannot be a Virtual DOM element
+   // `token-vault` is the element added in Step 1 above to `todo-react-app/index.html`
+   document.getElementById('token-vault') as HTMLElement
+ );
+
+ // Register the Token Vault Store
+ const tokenStore = register.store();

/**
   * Initialize the React application
   */
  (async function initAndHydrate() {

@@ collapsed @@
```

Registering the Token Vault Interceptor is what requests and registers the Service Worker. Calling `register.interceptor` returns the [`ServiceWorkerRegistration` object that can be used to unregister the Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration), as well as other functions, if that's needed. We won't be implementing that in this tutorial.

![Screenshot of the home page of the sample app with service worker active](../../_images/token-vault/home-page-with-interceptor-active.png)Figure 7. Sample app with Token Vault Interceptor active

Registering the Token Vault Proxy constructs the `iframe` component and mounts it do the DOM element passed into the method. It's important to note that this must be a real, available DOM element, not a Virtual DOM element. This results in the Token Vault Proxy being "registered" as a child frame and, therefore, accessible to your main app.

Calling `register.proxy` also returns an optional reference to the DOM element of the `iframe` that can be used to manually destroy the element and the Token Vault Proxy, if needed.

![Screenshot of the home page of the sample app with DOM showing iframe mounted](../../_images/token-vault/home-page-with-mounted-iframe.png)Figure 8. Sample app with Token Vault Proxy mounted in DOM

Finally, registering the store provides us with the object that replaces the default token store within the SDK. There are some additional convenience methods on this `store` object that we'll take advantage of later in the tutorial.

You will see a few errors in the console, but don't worry about those at the moment. The next steps will resolve them.

### Step 3. Replace the SDK's default token store

Within the existing SDK configuration, pass the `tokenStore` object we created in the previous step to the `set` method to override the SDK's internal token store.

React app `index.tsx` source file

```diff
@@ todo-react-app/src/index.tsx @@

@@ collapsed @@

  // Configure the SDK
  Config.set({
    clientId: import.meta.env.WEB_OAUTH_CLIENT,
    redirectUri: import.meta.env.REDIRECT_URI,
    scope: import.meta.env.OAUTH_SCOPE,
    serverConfig: {
      baseUrl: import.meta.env.AM_URL,
      timeout: import.meta.env.TIMEOUT,
    },
    realmPath: import.meta.env.REALM_PATH,
+   tokenStore, // this references the Token Vault Store we created above
  });

@@ collapsed @@
```

This configures the SDK to use the Token Vault Store, which is within the Token Vault Proxy, and that it needs to manage the tokens internally.

### Step 4. Check for existing tokens

Currently in our application, we check for the existence of stored tokens to provide a hint if our user is authorized. Now that the main app doesn't have access to the tokens, we have to ask the Token Vault Proxy if it has tokens.

To do this, replace the SDK method of `TokenStorage.get` with the Token Vault Proxy `has` method:

React app `index.tsx` source file

```diff
@@ todo-react-app/src/index.tsx @@

@@ collapsed @@

   let isAuthenticated = false;
    try {
-     isAuthenticated = !((await TokenStorage.get()) == null);
+     isAuthenticated = !!(await tokenStore?.has())?.hasTokens;
    } catch (err) {
      console.error(`Error: token retrieval for hydration; ${err}`);
    }

@@ collapsed @@
```

Note that this doesn't return the tokens as that would violate the security of keeping them in another origin, but the Token Vault Proxy will inform you of their existence. This is enough to hint to our UI that the user is likely authorized.

## Build and run the apps

At this point, all the necessary entities are set up. We can now run all the needed servers and test out our new application with Token Vault enabled.

Open three different terminal windows, all from within the root of this project. Enter each command in its own window:

First terminal window

```sh
npm run dev:react
```

Second terminal window

```sh
npm run dev:api
```

Third terminal window

```sh
npm run dev:proxy
```

Allow all the commands to complete the build and start the development servers.

Then, visit `http://localhost:5173` in your browser of choice. The to-do application should look and behave no different from before.

Open the dev tools of your browser, and proceed to sign in to the app. You will be redirected to the login page, and then redirected back after successfully authenticating. You may notice some additional redirection within the React app itself, this is normal.

Once you land on the home page, you should see the "logged in experience" with your username in the success alert.

To test whether Token Vault is successfully implemented, go to the Application or Storage tab of your dev tools and inspect the `localStorage` section.

You should see two origins: `http://localhost:5173`, our main app, and `http://localhost:5175`, our Token Vault Proxy.

Your user's tokens should be stored under the Token Vault Proxy origin on port `5175`, not under the React app's origin on port `5173`.

If you observe that behavior, then you have successfully implemented Token Vault. Congratulations, your tokens are now more securely stored!

![Screenshot of sample app with a logged-in user showing tokens in Token Vault under alternative origin](../../_images/token-vault/token-vault-demo-logged-in.png)Figure 9. To-do app home page with Token Vault and logged in

If you don't see the tokens in the Token Vault Proxy origin's `localStorage`, then follow the troubleshooting section below:

## Troubleshooting

### Getting failures in the service worker registration

Make sure your [dedicated Vite configuration](#interceptor-config) is correct.

Also, [check the actual file output](#interceptor-js) for the `interceptor.js` file. If the built file has ES Module syntax in it, or it looks incomplete, then it can cause this issue — Service Workers in some browsers, even the latest versions, don't support the same ES version or features as the main browser context.

### Tokens are not being saved under any origin

Open up your browser's dev tools and ensure the following:

1. Your Token Vault Interceptor is running under your main app's origin.

2. Do you have an `/access_token` request? It comes after the `/authorize` request and redirect.

3. Your Token Vault Interceptor is intercepting the `/access_token` request. If it is, you should see two outgoing requests: one for the main app and one for the Token Vault Proxy.

4. Your Token Vault Proxy is running within the `iframe` and forwarding the request.

5. There are no network errors.

6. There are no console errors.

|   |                                                                                  |
| - | -------------------------------------------------------------------------------- |
|   | Enabling Preserve Log for both the Console and the Network tabs is very helpful. |

### I'm getting a CORS failure

Make sure you have both origins listed in your [CORS configuration](#CORS).

Additionally, it's best if you use the Ping (ForgeRock) SDK template when creating a new CORS config in your server.

If both origins are listed, make sure you have no typos in the allowed methods. The methods like `GET` and `POST` are case-sensitive. Also, check the headers, which are NOT case-sensitive.

Allow Credentials must be enabled.
