NGINX LUA OAuth Proxy Plugin

NGINX LUA OAuth Proxy Plugin

Overview

The OAuth proxy plugin can be deployed with an NGINX LUA based reverse proxy. Its role is to translate secure cookies to access tokens that can be forwarded to APIs. This article will demonstrate a productive development setup and how to deploy the plugin.

Get the Code

First download the code from the GitHub repository, via the following command:

git clone https://github.com/curityio/nginx-lua-oauth-proxy-plugin

The repository files consist of the plugin’s LUA code and also some helper scripts for deployment and testing on a development computer:

Repo Files

Prerequisites

Ensure that you have these components installed on the local computer:

Supported Environments

The plugin is coded in the high level LUA programming language and can run in any NGINX based system with the LUA module enabled. To demonstrate this, a deployment script is included that can run the plugin in either Kong Open Source or OpenResty using Docker containers.

API Routes

The OAuth proxy plugin enables you to define your reverse proxy routes for APIs only once, so that both SPAs and other clients use the same API URLs:

_format_version: '2.1'
_transform: true
services:
- name: business-api
  url: http://apiserver:3001
  routes:
  - name: business-api-route
    paths:
    - /
  plugins:
  - name: oauth-proxy
    config:
      encryption_key: 4e4636356d65563e4c73233847503e3b21436e6f7629724950526f4b5e2e4e50
      cookie_name_prefix: example
      trusted_web_origins:
      - http://www.example.com

Routing will then work for various types of client, some of which may be Single Page Applications (SPA), and some of which may not use secure cookies:

Client TypeRouting Behavior
Mobile AppIf an authorization header is received, the plugin does no work and the token is simply forwarded to the API
Browser AppOtherwise the plugin expects to decrypt a secure cookie to get the access token and forward it to the API

If some API routes are unsecured and return public information, you can use the path features of the reverse proxy to exclude the plugin from those routes.

The OAuth proxy will receive secure cookies encrypted using AES256, and needs to be able to decrypt them, so must be configured with the same 32 byte (256 bit) symmetric key that the OAuth Agent uses. This is a 32 byte (256 bit) symmetric key, expressed as 64 hex characters, and can be initially generated using a command such as the following:

openssl rand 32 | xxd -p -c 64

Encryption keys should be renewed periodically as part of your deployments. Curity token handlers and the SPA Code Example handle this condition reliably. Users with existing sessions whose cookies use the old encryption key will need to sign in again, but will not experience any errors.

Deploy the Plugin

The Docker Compose file deploys the plugin in a reverse proxy which routes to a minimal API:

version: '3.8'
services:
  kong:
    image: kong:2.6.0-alpine
    hostname: kongserver
    ports:
      - 3000:3000
    volumes:
      - ./kong/kong.yml:/usr/local/kong/declarative/kong.yml
      - ../plugin:/usr/local/share/lua/5.1/kong/plugins/oauth-proxy
    environment:
      KONG_DATABASE: 'off'
      KONG_DECLARATIVE_CONFIG: '/usr/local/kong/declarative/kong.yml'
      KONG_PROXY_LISTEN: '0.0.0.0:3000'
      KONG_LOG_LEVEL: 'info'
      KONG_PLUGINS: 'bundled,oauth-proxy'
    profiles:
      - kong

  openresty:
    image: openresty/openresty:1.19.9.1-bionic
    hostname: openrestyserver
    ports:
      - 3000:3000
    volumes:
      - ./openresty/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf
      - ../plugin/access.lua:/usr/local/openresty/lualib/oauth-proxy.lua
    profiles:
      - openresty

  business-api:
    hostname: apiserver
    build:
      context: .
      dockerfile: ./api/Dockerfile

Run these commands to deploy the plugin within Kong Open Source:

cd deploy
./deploy.sh kong

Run these commands to deploy the plugin within OpenResty:

cd deploy
./deploy.sh openresty

Test the Plugin

Some basic integration tests are provided and use the curl tool to test various success and failure scenarios. These will help you to understand the plugin’s behavior, and the responses returned to your applications:

cd ../test
./test.sh

The plugin follows OWASP Cross Site Request Forgery (CSRF) Best Practices for calls to APIs with secure cookies. This includes verifying the web origin and applying double submit cookie checks.

5. Testing CORS headers for error responses to the SPA ...
5. CORS error responses to the SPA have the correct headers
6. Testing GET with a valid encrypted cookie ...
6. GET with a valid encrypted cookie was successfully routed to the API
{
  "accessToken": "42665300-efe8-419d-be52-07b53e208f46"
}
7. Testing POST with missing CSRF cookie ...
7. POST with a missing CSRF cookie was successfully rejected
{
  "code": "unauthorized",
  "message": "The request failed cookie authorization"
}
8. Testing POST with missing CSRF header ...
8. POST with a missing CSRF header was successfully rejected
{
  "code": "unauthorized",
  "message": "The request failed cookie authorization"
}
9. Testing POST with incorrect CSRF header ...
9. POST with an incorrect CSRF header was successfully rejected
{
  "code": "unauthorized",
  "message": "The request failed cookie authorization"
}
10. Testing POST with correct CSRF cookie and header ...
10. POST with correct CSRF cookie and header was successfully routed to the API
{
  "accessToken": "42665300-efe8-419d-be52-07b53e208f46"
}

During execution you can view details of invalid and valid requests written to NGINX logs:

Proxy Logs

Access Token Types

The plugin can work with secure cookies that contain either of these access token types:

Access Token TypeBehavior
OpaqueThis keeps cookie sizes small, in which case a phantom token plugin is run next to get a JWT
JWTThis leads to large cookie sizes that can exceed default HTTP header size limits allowed by NGINX

The opaque option is recommended and is used by the SPA with Token Handler code example. In the event that you need to use larger JWTs you will need to set some extra environment variables for NGINX, with values similar to these:

Environment VariableExtended Setting
proxy_buffers8 16k
proxy_buffer_size16k
large_client_header_buffers8 16k

Conclusion

The OAuth proxy plugin enables you to handle different message credentials for API requests in a central place, and only the plugin needs to know the cookie decryption key. This enables you to use the strongest browser security in your SPAs, without adding complexity to APIs. It also ensures that web and mobile clients can send requests to the same API URLs.