Node.js OAuth Agent

Node.js OAuth Agent

On this page

Note

Curity Identity Server is used in this example, but other OAuth servers can also be used.

Overview

The role of the OAuth Agent in the below diagram is to handle the OpenID Connect flow for Single Page Applications (SPA). This includes issuing of secure cookies for the browser and managing follow-on operations for token refresh, retrieving user info and signing out:

Token Handler

This implementation is coded in Node.js and Express, and is the preferred choice of OAuth agent for companies who use Javascript or Typescript code to develop cloud native microservices. If you need to make minor changes, for whatever reason, you can fork the repo and adapt the code.

The SPA security then uses the Code Flow in the most mainstream way, with response_type=code, PKCE, and a client secret. Only the strongest HTTP Only, SameSite=strict cookies are used in the browser. In addition, microservices only need to work with JWT access tokens, and do not need to deal with any web security concerns.

OAuth Agent Operations

The following operations represent the OAuth Agent's API interface, and all of these are designed to serve SPAs via Ajax requests from the browser. This enables the SPA to fully control behavior such as when redirects occur:

EndpointDescription
POST /login/startStart a login by providing the request URL to the SPA and setting temporary cookies.
POST /login/endValidate the request, complete the login and return HTTP only encrypted cookies to the browser.
GET /userInfoReturn Personally Identifiable Information (PII), such as the user name, to the SPA.
GET /claimsReturn claims from the ID token to the SPA, containing authentication related information.
POST /refreshRefresh the current access token and rewrite secure cookies.
POST /logoutClear secure cookies and return an end session request URL.

An OAuth Agent is a tricky API to develop, since its main focus is on browser and OAuth infrastructure:

BehaviorDescription
Authorization CodesThe API needs to receive real authorization codes in order to get tokens.
HTTP RedirectsThe SPA client therefore needs to perform real HTTP redirects and user logins.
Cross Origin RequestsThe API and its SPA client may need to deal with Cross Origin Request Sharing (CORS).
CookiesThe API operations need to frequently read, write and update encrypted cookies containing tokens.

Get the Code

First get the code, which is a standard Node.js API that uses the Express HTTP server, and the repository contains both code and test resources. Ensure that an up to date version of Node.js is installed, according to the GitHub repository's README instructions.

Node.js Resources

The logic for the OAuth Agent can be studied and adapted if needed. The controller source files provide an outline of processing, including cookie issuing. To understand the main interaction with the Authorization Server, see the authorizeUrl and getToken modules within the lib folder.

Configure Development URLs

When running on a development computer, the OAuth Agent is configured to use the following base URLs. The default setup uses plain HTTP URLs in order to simplify infrastructure on a development computer, though of course the OAuth Agent should be updated to use SSL for deployed environments:

ComponentBase URL
OAuth Agenthttp://api.example.local:8080/oauth-agent
Curity Identity Server Endpointshttp://login.example.local:8443
Curity Identity Server Admin UIhttps://localhost:6749/admin

For URLs to work you will need to add the following entries to the local computer's hosts file:

bash
12
127.0.0.1 api.example.local login.example.local
:1 localhost

Run the OAuth Agent

This is done in the standard Node.js manner, after which the API's HTTP endpoints are available for a client to call:

bash
12
npm install
npm start

You can then simulate a request from the SPA via the following test command, to ensure that connectivity is working:

bash
12
curl -X POST http://api.example.local:8080/oauth-agent/login/start \
-H "origin: http://www.example.local" | jq

Run Integration Tests

When working on the OAuth Agent as an API it is useful to initially avoid a browser and use a test driven approach. Integration tests use Wiremock to mock the endpoints of the Curity Identity Server. Run all integration tests with these commands, to start the API and wiremock, then make HTTP requests:

bash
123
npm start
npm run wiremock
npm test

The OAuth Agent's endpoints are then called, and a number of success and failure scenarios are verified. This includes running an entire OAuth lifecycle, from login through to session expiry and logout:

Integration Tests

Run End-to-End Tests

In order to run tests that use an instance of the Curity Identity Server, first ensure that the following components are also installed:

You will also 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. Copy the license file into the test/idsvr folder.

Next, ensure that the OAuth Agent is running, then execute the deployment script, to spin up a Docker based instance of the Curity Identity Server:

bash
1
./test/end-to-end/idsvr/deploy.sh

You can then log in to the Admin UI with credentials admin / Password1 to view client configuration details. OAuth requests on behalf of the SPA are initiated from the OAuth Agent, which uses a client secret when it calls the Authorization Server:

Standard Secret

Then run the following script based client, which sends the same HTTP requests as an SPA, but avoids the need to continually switch to a browser and extract values such as authorization codes. This scripted client uses the popular curl and jq tools:

bash
1
./test/end-to-end/test-oauth-agent.sh

The script runs a complete SPA workflow, using a preconfigured user account shipped with the Docker deployment. This begins with a login, then continues with refresh, user info and claims requests, finishing with a logout. This fast feedback will enable any coding bugs in your own OAuth Agent to be quickly found and ironed out:

Base Token Handler Workflow

OpenID Connect Messages

The OAuth Agent returns front channel request URLs to the SPA, which then updates the browser location to this URL, to begin the user authentication process. At this point, the OAuth Agent has also written a temporary secure cookie containing state and code_verifier values.

text
12345678
GET http://login.example.local:8443/oauth/v2/oauth-authorize
?client_id=spa-client
&redirect_uri=http%3A%2F%2Fwww.example.com%2F
&response_type=code
&code_challenge=l9QIPE4TFgW2y7STZDSWQ4Y4CQpO8W6VtELopzYHdNg
&code_challenge_method=S256
&state=NlAoISfdL1DxPdNGFBljlVuB1GDjgGARmqDcxtHhV8iKNYu6ECS2KOavDHpI3eLN
&scope=openid%20profile

When authentication completes, the SPA sends the OAuth Agent the response URL containing the authorization code. The OAuth Agent then validates the response using the temporary cookie, then sends a back channel request to the Authorization Server, along with a client secret:

text
12345678
POST http://login.example.local:8443/oauth/v2/oauth-token
grant_type=authorization_code&
client_id=spa-client&
client_secret=Password1&
redirect_uri=http://www.example.com/&
code=YFFX2HmFNnrMS8alWIZH83oim9ZHgwRh&
code_verifier=ItJtBXUGtHs-3FpUHB8qW9uJ00XcwTfeiZdLGquawMg

Tokens returned in the response are then stored in HTTP Only encrypted secure cookies that are returned to the browser but not accessible from Javascript code. AES256 symmetric encryption is used to encrypt the cookies, with a key only known to the OAuth Agent.

SPA Integration

The Example SPA can use a deployed instance of the Node.js OAuth Agent, by running the SPA deployment with the following commands. For further details on how deployment works, see the Deployment Example.

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

Conclusion

An OAuth Agent is a tricky component to develop, so Curity have provided reference implementations that can be simply plugged in. If required, you can adapt Curity's implementation and make small code changes, using the API development steps from this tutorial. When dealing with high-worth data, this enables you to ramp up security even further, as demonstrated in the Financial-grade OAuth Agent example.