NGINX Lua OAuth Proxy Plugin

NGINX Lua OAuth Proxy Plugin

On this page

Overview

The OAuth proxy plugin can be deployed with an NGINX LUA based reverse proxy or API gateway. Its role is to deal with secure cookies and cross origin permissions, rather than needing to complicate code within your APIs. This article will show how to run, deploy and test the plugin.

Get the Code

First download the code from the GitHub repository, via the following command. The repository files consist of the plugin's LUA code and also some helper scripts for deployment and testing on a development computer:

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

Prerequisites

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

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 Route Configuration

When using Kong, the OAuth proxy plugin is configured for an API or path using the required settings shown below. This enables it to return CORS headers for the SPA, and to decrypt received cookies:

_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:
      cookie_name_prefix: example
      encryption_key: $ENCRYPTION_KEY
      trusted_web_origins:
      - http://www.example.com
      cors_enabled: true

When using OpenResty, the equivalent configuration looks like this:

location ~ ^/ {

    rewrite_by_lua_block {

        local config = {
            cookie_name_prefix = 'example',
            encryption_key = os.getenv('ENCRYPTION_KEY'),
            trusted_web_origins = {
                'http://www.example.com'
            },
            cors_enabled = true
        }

        local oauthProxy = require 'resty.oauth-proxy'
        oauthProxy.run(config)
    }

    proxy_pass http://apiserver:3001;
}

If required, the allow_tokens optional property can be used to pass requests that already contain a bearer token directly to the API, without applying cookie logic. In some setups this enables the same API routes to be used for both web and mobile clients. 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.

Cross Origin Resource Sharing (CORS)

The plugin can return CORS Response Headers needed for the SPA and API to interact, and it is preferred to manage this concern in the gateway rather than complicating your API code. By default the plugin will return the following headers, and a number of optional CORS settings can be used for finer control. These are described in the README file of the GitHub repository.

access-control-allow-origin: http://www.example.com
access-control-allow-credentials: true
access-control-allow-methods: OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE
access-control-allow-headers: x-example-csrf
access-control-max-age: 86400

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, and this must be represented as 64 hex characters. The encryption key 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 handler components 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 simplest way to deploy the plugin is to use a custom Dockerfile and luarocks install, which deploys both the plugin and its dependencies. It is also possible to clone the GitHub repo and manually copy files to the locations described in this section.

Kong Deployment

The below example Dockerfile also installs the Kong Phantom Token Plugin. The luarocks command uses git to download LUA files, and it may be necessary to run git config commands first, depending on your environment.

FROM kong:3.0.0-alpine
USER root
RUN git config --global url."https://".insteadOf git:// && \
    git config --global advice.detachedHead false && \
    luarocks install kong-oauth-proxy   1.3.0 &&
    luarocks install kong-phantom-token 2.0.0
USER kong

This deploys three files called handler.lua, schema.lua and access.lua to the docker image, inside an oauth-proxy folder within the Kong plugins location, which is a path such as /usr/local/share/lua/5.1/kong/plugins. The Docker Compose file then needs to reference the additional plugins in the KONG_PLUGINS environment variable.

version: '3.8'
services:
  kong:
    image: custom_kong:3.0.0-alpine
    hostname: kongserver
    ports:
      - 3000:3000
    volumes:
      - ./kong/kong.yml:/usr/local/kong/declarative/kong.yml
    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,phantom-token'
    profiles:
      - kong

OpenResty Deployment

The equivalent command to build the OpenResty custom Docker image is almost the same, and this example also deploys the OpenResty Phantom Token Plugin:

FROM openresty/openresty:1.21.4.1-bionic

RUN apt-get update && apt-get install git -y
RUN git config --global url."https://".insteadOf git:// && \
    git config --global advice.detachedHead false && \
    luarocks install lua-resty-oauth-proxy   1.3.0 && \
    luarocks install lua-resty-phantom-token 2.0.0

This deploys an oauth-proxy.lua file to the docker image, inside a resty folder within the lua_package_path, at a location such as /usr/local/openresty/lualib/resty. The Docker Compose file then needs to reference an NGINX configuration:

version: '3.8'
services:
  openresty:
    image: custom_openresty/openresty:1.21.4.1-bionic
    hostname: openrestyserver
    ports:
      - 3000:3000
    volumes:
      - ./openresty/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf
    profiles:
      - 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. First deploy a local Docker image for your preferred Lua platform:

cd docker
./deploy.sh kong
./deploy.sh openresty

Then run some curl based tests to act as a Single Page Application:

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. The opaque option is recommended and is used by the SPA using Token Handler Pattern code example.

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

Browsers and HTTP servers typically have a 4KB per cookie limit, so aim to stay within that. If the overall size of the cookie header exceeds 4KB then NGINX requires you to change server properties such as proxy_buffers, proxy_buffer_size and large_client_header_buffers. See the NGINX HTTP Proxy Module documentation for further details.

Conclusion

The OAuth proxy plugin enables you to handle cookie and CORS aspects centrally in the API gateway, and the cookie decryption key is also managed there. This enables you to use the strongest browser security in your SPAs without impacting the code or deployment of your APIs.