Backend for Frontend Tutorial

Backend for Frontend Tutorial

tutorials

Overview

This tutorial and video explain how to run an end-to-end code example for a Single Page Application (SPA) that implements OpenID Connect using a Backend for Frontend approach.

This is Curity’s recommended solution for best browser security, simple code and separating concerns. There are quite a few moving parts that need to be understood though.

Traditional Single Page Apps

In recent years it has been common to implement OpenID Connect for SPAs solely in Javascript. This is no longer recommended in OAuth for Browser Based Apps however, for two main reasons:

  • Using access tokens in the browser has more security risks than using secure cookies, so SPAs should instead use cookies with recent SameSite improvements
  • Recent browser changes to prevent tracking may result in dropped third party SSO Cookies during traditional SPA flows, leading to blocking usability issues

To solve these problems it is now recommended to use a Backend for Frontend approach, where the SPA security is routed via a back end component that can manage token storage and issuing of secure cookies for the browser.

Backend for Frontend (BFF)

The Backend for Frontend implementation requires some REST API endpoints, whose role is to perform the following tasks:

TaskDescription
Handle OAuth RequestsThe BFF does the work of calling the Authorization Server on behalf of the SPA
Issue SameSite CookiesThe BFF issues application level secure cookies for the SPA’s web origin

By separating the Web and API concerns, it is possible to achieve the following benefits:

  • Recommended Browser Security
  • Simple SPA Code
  • Deploy Anywhere
  • Good User Experience

Components

The end-to-end solution for an SPA now includes a couple of extra components, illustrated below:

Components

A software company provides the following application components, which only require simple OAuth code:

ComponentDescription
Single Page AppThe web app, which for this code example is a simple app implemented with React
Web Content DeliveryThis serves web static content, and many companies use a Content Delivery Network (CDN)
MicroservicesIn the code example the SPA calls a simple NodeJS API via a reverse proxy

These two toolbox components are also required, and software companies should be able to plug in a third party implementation:

ComponentDescription
BFF APICalled by the SPA to perform the OAuth work and then issues SameSite cookies to the browser
Reverse Proxy PluginsThese receive the secure cookie from browsers, then look up JWTs and forward them to microservices

Curity API Components

Curity provide an initial implementation of the toolbox components, which customers can use to implement their SPA security. At the time of writing, these components are at a demo level to enable the design patterns to be understood.

If these components do not meet your needs they can be adapted as required. The solution is not Curity specific and these components will work with any standards based Authorization Server.

Run the End-to-End Solution

Install Prerequisites

First ensure that you have both NodeJS and Docker Desktop installed, since these components will be used to build and deploy the browser and API code.

Configure Domains

The code example uses the following test domains as one possible deployment setup:

DomainUsage
www.example.comThe SPA’s web origin, whose web host provides static content to the browser
api.example.comThe domain where microservices and the Backend for Frontend are hosted
login.example.comWhere the Curity Identity Server is hosted, which issues tokens for the UI and API

These domains must first be added to your /etc/hosts file as follows:

127.0.0.1 localhost www.example.com api.example.com login.example.com
:1        localhost

BFF API Hosting

For SameSite cookies to work, the BFF API must be hosted on the same parent domain as the SPA’s web origin, and the code example uses a value of example.com.

Get the Code

Clone the repository with the following commands:

git clone https://github.com/curityio/web-oauth-via-bff
cd web-oauth-via-bff

Then view the code components and note that these all require only simple code. The Code Example article describes how the OAuth work for the SPA is implemented.

SPA Code

Build the Code

Next run the build script, which compiles the code for all components and deploys it to Docker images:

cd code
./build.sh

Note that this also downloads and builds the BFF API within the code folder, and you can study its code or adapt it if required:

BFF Code

Supply a Curity Identity Server License File

Before deploying the system you will need a license file for the Curity Identity Server. If you do not have one you can get a free community license from the Curity Developer Portal by signing in with your GitHub account.

Docker Components

The deployment folder contains these components in total:

ComponentDescription
Web HostA simple web host to serve the SPA’s static content
Example APIA simple API that the SPA uses to access data, and which receives JWTs
BFF APIThe Curity provided BFF API implements the OAuth work and cookie issuing
Reverse ProxyThe code example uses Kong Open Source as the reverse proxy, along with two plugins
Curity Identity ServerBoth the BFF API and the reverse proxy are configured to call the Identity Server
Curity SQL DatabaseA database containing a well known test user name and password

These are all deployed to a development computer via a Docker Compose file:

Docker Compose

Deploy the System

The entire system can be deployed via the following commands:

cd deployment
./deploy.sh

This script downloads two reverse proxy plugins to the deployment folder, and you can study or adapt plugin behaviour if required. We will look at this plugin’s behavior shortly:

Plugin Code

Browse to the SPA

You can then browse to http://www.example.com to run the SPA, which will initially present an unauthenticated view:

Unauthenticated View

You can then sign in with the test user demouser using password Password1, after which an authenticated view is displayed. This can be used to get data from the BFF API’s userinfo endpoint and from the Example API:

Authenticated View

Multi-Tab Browsing

Next test some multi tab and navigation operations. These areas can sometimes be a source of problems for web OAuth solutions, but note that the code example handles them reliably:

  • Reload the page via a keystroke such as Cmd+R
  • Open multiple browser tabs or windows and test signing in or out
  • Follow an email link to your authenticated session

View Logs

While using the system you can look at logs to understand what is happening in the BFF API or reverse proxy:

Deployed Components

When troubleshooting, it can be useful to view the log output of only a specific component in its own terminal window. The following standard Docker commands can be used for this:

export BFF_API_CONTAINER_ID=$(docker container ls | grep bff-api | awk '{print $1}')
docker logs -f $BFF_API_CONTAINER_ID
export KONG_CONTAINER_ID=$(docker container ls | grep reverse-proxy | awk '{print $1}')
docker logs -f $KONG_CONTAINER_ID

Browser Security Overview

Website OAuth Flow

The security used by the BFF API and SPA is equivalent to that used by a website based app, with these main characteristics:

  • Code Flow + PKCE, along with the use of a client secret
  • Use of recent SameSite cookie improvements

BFF Benefits

The BFF pattern does not provide better security than a traditional website app, but provides a better separation of concerns, leading to these benefits:

BenefitDescription
Simple CodeWeb developers do not need to run a web backend such as Java to receive web content
Deploy AnywhereThe SPA can be deployed to any host, including a Content Delivery Network (CDN)
Good UsabilityThe SPA does not experience any abrupt login redirects or navigation problems due to dropped cookies

Developer Setup

Web developers will need to run the BFF component on their workstation, and the preferred way to do this is to use a Docker container. Curity may provide additional options for developers in future, such as integrating with the Webpack Dev Server.

OAuth Messages

The BFF API returns an Authorization Code Flow + PKCE request URL to the SPA, with parameters similar to these:

GET /oauth/v2/oauth-authorize
    ?client_id=spa-client
    &redirect_uri=http%3A%2F%2Fwww.example.com%2F
    &response_type=code
    &code_challenge=k2XFDLJ_mIVEt25-phu4_3O4Jqrd8QqTGGbeEsql3Rc
    &code_challenge_method=S256
    &state=NlAoISfdL1DxPdNGFBljlVuB1GDjgGARmqDcxtHhV8iKNYu6ECS2KOavDHpI3eLN
    &scope=openid%20profile

After login, the BFF API sends a request payload similar to the following to the Curity Identity Server, to exchange the code for tokens:

POST /oauth/v2/oauth-token

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

When an access token expires, the SPA can ask the BFF API to refresh access tokens. The BFF then sends a request payload similar to the following to the Authorization Server:

POST /oauth/v2/oauth-token

grant_type=refresh_token&
client_id=spa-client&
client_secret=Password1&
refresh_token=76fd951484a3d4cc31f869bb8fc93df0:0e11f2b7bbe29d809203e53efd3d92eebf173376759c8407413ea6db2ddaef05b572feaeab8b702fdf633584dc1a0af8

These messages improve upon the security of older SPA solutions, since a client secret can be used, and since no tokens are used in the browser.

Secure Cookies Issued

In total the BFF API uses 5 different secure cookies, with different data in each. Each of the cookies are strongly protected using AES256 symmetric encryption, with the encryption key known only by the server:

Cookie NameContains
example-loginA temporary cookie used between start / end login, to verify that the state before and after login matches, and to store the PKCE code verifier
example-authThe main SPA cookie contains the refresh token representing the user’s authenticated session
example-idThis cookie contains the ID token, which the BFF API provides to the SPA via its /userInfo endpoint
example-atThis cookie contains the current access token, which is used later when calling APIs
example-csrfThis cookie can be used for additional CSRF protection, as described in OWASP CSRF defense in depth

All cookies have the following properties, though the paths of the refresh and ID cookies are restricted to only the BFF API endpoints where they are needed.

PropertyDescription
HTTP OnlyThe cookie cannot be read by Javascript code
SecureThe cookie requires an SSL connection
SameSite=strictThe cookie can only be sent from the web origin, and not from malicious domains
Domain=api.example.comThe cookie is only needed when calling APIs
Path=/The access token cookie is available to all APIs within the API domain

Note that the SPA origin is www.example.com but that cookies only need to be issued to the API domain at api.example.com, which is in the Same Site. An SPA should not need to send cookies during web requests, so it is preferred to limit the scope of the cookie to API requests.

If you have particular requirements then the cookie domain can be extended to .example.com and also sent during web requests. Note however that this increases the potential for this type of usability problem:

  • User signs in
  • User follows an email link back to their authenticated session
  • The cookie is dropped because it is SameSite=strict
  • The user is redirected to re-authenticate again

OAuth Client Settings

You can login to the Admin UI to view the OAuth client settings for the SPA, using these connection details:

  • https://localhost:6749/admin
  • User: admin
  • Password: Password1

The spa-client used by the BFF API has the following Curity configuration settings, which are equal to those of a website app:

<client>
    <id>spa-client</id>
    <client-name>spa-client</client-name>
    <description>SPA that uses a Back End for Front End API</description>
    <secret>Secret1</secret>
    <redirect-uris>http://www.example.com/</redirect-uris>
    <scope>openid</scope>
    <scope>profile</scope>
    <user-authentication>
        <allowed-post-logout-redirect-uris>http://www.example.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>

Secure Development

The OAuth security in the code example follows current best practices for browser security, but of course this does not make your app fully secure. Software companies must also follow a security development lifecycle, and in particular protect against Cross Site Scripting (XSS). If there is an XSS vulnerability then secure cookies could be abused by an attacker to steal data.

API Security Overview

API Request Routing

Once authentication has been completed, the SPA will make API calls to access data, as described in the Code Example article. Your APIs will expect to receive JWTs and the usual way to manage this is via the Phantom Token Pattern.

For SPAs we now have an additional requirement, to decrypt the secure cookie and enforce CSRF checks before the phantom token pattern runs. The overall workflow will look like this:

API Flow

Reverse Proxy Behavior

The reverse proxy is the preferred place to implement this type of utility operation, rather than complicating your API code. The kong.yml configuration for the code example focuses only on the routing of web requests to a single API:

- name: business-api
  url: http://apiserver:3002
  routes:
  - name: business-api-route
    paths:
    - /api

  plugins:

  - name: bff-token
    config:
      encryption_key: NF65meV>Ls#8GP>;!Cnov)rIPRoK^.NP
      cookie_name_prefix: example
      verify_csrf: true
      trusted_web_origins:
      - http://www.example.com
  
  - name: phantom-token
    config:
      introspection_endpoint: http://curityserver:8443/oauth/v2/oauth-introspect
      client_id: api-gateway-client
      client_secret: Password1
      token_cache_seconds: 900
      trusted_web_origins:
      - http://www.example.com

The plugins used are implemented in the LUA Scripting Language and run in this sequence:

PluginDescription
BFF TokenDecrypts the secure cookie containing the access token, then forwards the opaque access token to the next plugin
Phantom TokenIntrospects the opaque token to get a JWT to forward to the API, then caches the JWT for subsequent requests

BFF Token Plugin

The only new reverse proxy component is the BFF token plugin, which deals with decrypting cookies and forwarding the access token. For data changing commands, the plugin can also verify that a CSRF request header matches that in the CSRF cookie.

End-to-End Data Access

The example API included with the code example requires a valid JWT for every API request, and returns a 401 error if this is not the case. The below Get Data button allows API calls to be tested all the way through, and you can view reverse proxy logs to see the plugins being used:

Authenticated View

Deployment Variations

The deployment setup for the code example consists only of a bff path and a single api path, in order to explain the routing mechanics. Real world systems often deal with multiple web domains, multiple types of client or alternative technical setups.

Multiple Branded Web Domains

In some companies the microservices are hosted on a domain such as api.example.com but the web apps are hosted on branded domains named after products. In this setup an additional API domain would be spun up, as a child or sibling of the web domain:

Branded Domains

BFF Technology Choices

The BFF API can be implemented and deployed in various forms and some examples are listed below. Curity will be providing additional implementations in order to give software companies additional choices.

  • Container Based API
  • Serverless Lambda
  • Reverse Proxy Plugin
  • Web Host Extension

We recommended implementing API routing via a reverse proxy or API gateway rather than in code though, since these components have proven routing and high throughput capabilities.

Multiple Types of Client

Using BFF means that there will be different entry points to microservices for web clients and other clients. This type of setup is quite common already for APIs that require Mutual TLS connections from business partners:

Client TypeSecurity Model
Mobile clientsAccess tokens are used to call APIs via the default entry point
Web clientsSecure cookies are used to call APIs via a web entry point
B2B clientsAccess tokens are used to call APIs over a Mutual TLS channel

A reverse proxy makes it easy to support multiple API security models via different entry points, but without impacting underlying microservices. A possible configuration for an Orders API is shown below, where the reverse proxy manages the different entry points, so that the API receives all requests on the same URL and only needs to work with JWTs:

- name: orders-api
  url: http://apiserver:3002
  routes:

  - name: default
    paths:
    - /api/orders
    plugins:
      - name: phantom-token

  - name: web
    paths:
    - /api/web/orders
    plugins:
      - name: bff-token
      - name: phantom-token

  - name: mutual-tls
    paths:
    - /api/b2b/orders
    plugins:
      - name: mutual-tls
      - name: phantom-token

Video

The following video visually shows how to deploy the code example, then uses the deployed system and explains some further details:

Conclusion

We demonstrated an end-to-end solution where the main components a company develops can continue to be implemented in a simple and standard way:

  • SPAs make simple REST requests to an API to perform their OAuth work
  • SPAs can be deployed to any web host
  • Microservices continue to validate JWTs in the standard way

Meanwhile the more complex plumbing was implemented once as Curity provided toolbox components, which would need to be deployed for each of your web domains. This separation of concerns leads to web apps that uses modern and recommended security. The separated architecture is then very easy to scale and extend.

Keep up with our latest articles and how-tos RSS feeds.