skip to Main Content

I set up a simple docker-compose deployment with a nginx default.conf.template.

docker-compose.yml

services:
  nginx:
    build: ./nginx
    ports:
      - "80:80"
    volumes:
    - ./nginx/nginx.conf:/etc/nginx/nginx.conf
    - ./nginx/default.conf.template:/etc/nginx/templates/default.conf.template
    environment:
      NGINX_HOST: localhost
      NGINX_PORT: 80
      BACKEND1_HOST: backend1
      BACKEND1_PORT: 8081
      BACKEND2_HOST: backend2
      BACKEND2_PORT: 8082
      BACKEND3_HOST: backend3
      BACKEND3_PORT: 8083
      INTROSPECTION_CLIENT_ID: api-gateway-client
      INTROSPECTION_CLIENT_SECRET: Password1
      INTROSPECTION_ENDPOINT: http://localhost:8443//oauth/v2/oauth-introspect
    depends_on:
      - backend1
      - backend2
      - backend3

  backend1:
    build:
      context: ./backend1
    ports:
      - "8081:8081"

  backend2:
    build:
      context: ./backend2
    ports:
      - "8082:8082"

  backend3:
    build:
      context: ./backend3
    ports:
      - "8083:8083"

nginx.conf


user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

# phantom module
load_module modules/ngx_curity_http_phantom_token_module.so;

events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

default.conf.template

proxy_cache_path cache levels=1:2 keys_zone=api_cache:10m max_size=10g inactive=60m use_temp_path=off;

server {
    server_name  ${NGINX_HOST};
    listen       ${NGINX_PORT};

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    location /service1/ {
        proxy_pass http://${BACKEND1_HOST}:${BACKEND1_PORT};
        phantom_token on;
        phantom_token_client_credential ${INTROSPECTION_CLIENT_ID} ${INTROSPECTION_CLIENT_SECRET};
        phantom_token_introspection_endpoint curity;
        phantom_token_scopes phone;
    }

    location /service2/ {
        proxy_pass http://${BACKEND2_HOST}:${BACKEND2_PORT};
        phantom_token on;
        phantom_token_client_credential ${INTROSPECTION_CLIENT_ID} ${INTROSPECTION_CLIENT_SECRET};
        phantom_token_introspection_endpoint curity;
        phantom_token_scopes phone;
    }

    location /service3/ {
        proxy_pass http://${BACKEND3_HOST}:${BACKEND3_PORT}/hello;
        phantom_token on;
        phantom_token_client_credential ${INTROSPECTION_CLIENT_ID} ${INTROSPECTION_CLIENT_SECRET};
        phantom_token_introspection_endpoint curity;
        phantom_token_scopes read;
    }

    location curity {
        proxy_pass ${INTROSPECTION_ENDPOINT};
        proxy_cache_methods POST;
        proxy_cache api_cache;
        proxy_cache_key $request_body;
        proxy_ignore_headers Set-Cookie;
    }
}

I then request a token with:

curl -u 'api-client:Password1' -X POST http://localhost:8443/oauth/v2/oauth-token -d grant_type=client_credentials -d scope=phone

Notice that the scope phone is used.

Now I take the token from the oauth-token response and add it as authorization header to make a request to the service3:

curl -H "Authorization: Bearer _0XBPWQQ_16d5e094-c3b4-4a40-8302-ea19ed982860" http://localhost:80/service3/

What I was expecting was that the nginx phantom token module would reject my request to service3, because the scope "read" is not present in the token, but it does not. Instead the request is reverse proxied to the service3 and the backend handler is called.

According to the docs: https://github.com/curityio/nginx_phantom_token_module?tab=readme-ov-file#phantom_token_scopes

Am I right in assuming that the request should not be forwarded if the scope read is not included in the jwt ?

2

Answers


  1. Chosen as BEST ANSWER

    Many thanks Michal for pointing this out to me.

    You are right regarding validating the parameters in the API.

    I tested the flow with the lua plugin and openresty. Works like a charm.


  2. Not in this plugin, no. This is the description from the docs:

    The space-separated list of scopes that the server should inform the client are required when it does not provide an access token.

    The plugin uses the value from the phantom_token_scopes setting only when the client does not provide an access token at all. The value from this setting is used to populate the response’s WWW-Authenticate header. Scopes are not validated in the gateway by this plugin.

    If you are able to switch to Lua plugins, you can use this version:
    https://github.com/curityio/nginx-lua-phantom-token-plugin?tab=readme-ov-file#scope As you can see, the Lua plugin performs validation of the scope parameter in the gateway.

    Remember that even if you validate the parameter in the gateway, your APIs should validate it again. The gateway validation should be treated as a fail-fast solution that limits unwanted traffic. Your APIs should not trust incoming traffic and validate all the parameters themselves, in case someone manages to circumvent your API gateway.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search