Skip to main content

Send NGINX permission checks to the Permit PDP

Send a permission check to the Permit.io policy decision point (PDP) for each request that NGINX serves, with the NGINX ngx_http_auth_request_module. This page is for developers and operators who run NGINX in front of their applications. Every request to a protected location produces a decision you can read in the Permit audit log, and this page names the component you have to add to block a denied request.

The auth_request module cannot block a denied check on its own

The PDP's /nginx_allowed endpoint answers HTTP 200 for both allowed and denied checks, and puts the decision in the allow field of the response body (is_allowed_nginx in horizon/enforcer/api.py of the PDP repository declares status_code=status.HTTP_200_OK). The auth_request module decides from the subrequest's status code alone, so with the configuration on this page NGINX forwards every request, including the ones the policy denies. To block denied requests, add a component that reads the response body, as Block denied requests describes. Don't treat the auth_request configuration alone as an enforcement layer.

How the NGINX integration works

  1. A client request reaches an NGINX location that has an auth_request directive.

  2. NGINX sends a subrequest to an internal location, which proxies it to the PDP's /nginx_allowed endpoint.

  3. The PDP reads four request headers and runs a permission check:

    HeaderCheck field
    permit-user-keyUser key
    permit-actionAction
    permit-resource-typeResource type
    permit-tenant-idTenant
  4. The PDP returns HTTP 200 with a JSON body whose allow field is true or false, and records the decision in the Permit audit log.

  5. NGINX reads the status code of the subrequest and forwards the request. See the ngx_http_auth_request_module documentation.

Prerequisites

Configure NGINX

1. Check that NGINX has the auth_request module

Print the NGINX build options:

nginx -V

The output contains --with-http_auth_request_module when the module is available.

2. Confirm the PDP URL

Make sure the PDP is running and reachable from the NGINX host. The NGINX configuration in the next step uses http://localhost:7766. If your PDP runs at another address, change the proxy_pass URL to match.

3. Add the auth_request configuration

Edit your NGINX configuration file, usually /etc/nginx/nginx.conf or a site file in /etc/nginx/sites-available/:

http {
# ... other configurations ...

server {
listen 80;
server_name example.com;

location / {
auth_request /auth;

# ... your existing location configuration ...
}

location = /auth {
internal;
# Forward the request to your deployed PDP
proxy_pass http://localhost:7766/nginx_allowed;
proxy_method POST;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_set_header Authorization "Bearer <YOUR_API_KEY>";

# Pass the required Permit.io headers
proxy_set_header permit-user-key $http_permit_user_key;
proxy_set_header permit-action $http_permit_action;
proxy_set_header permit-resource-type $http_permit_resource_type;
proxy_set_header permit-tenant-id $http_permit_tenant_id;
}
}
}

In this configuration:

  • The location / block sends a check for every path with auth_request /auth.
  • The internal location = /auth block proxies the subrequest to the PDP's /nginx_allowed endpoint as a POST request without a body, adds your environment API key (replace <YOUR_API_KEY>) in the Authorization header, and copies the four permit-* headers from the client request. The PDP answers 401 for a request without the Authorization header.
The client sets the permit-* headers

$http_permit_user_key and the other $http_permit_* variables hold header values that the client sent. If clients reach NGINX directly, a client can send another user's key and get that user's permissions. Set the permit-* headers from a trusted source, such as an authentication layer in front of NGINX that removes client-supplied permit-* headers.

4. Restart NGINX

Restart NGINX to load the configuration. The command depends on how you run NGINX. On a host managed by systemd:

sudo systemctl restart nginx

Verify that the check reaches the PDP

  1. Send a request to NGINX with the four headers:

    curl -H "permit-user-key: john@permit.io" \
    -H "permit-action: read" \
    -H "permit-resource-type: document" \
    -H "permit-tenant-id: default" \
    http://example.com/
  2. Open the Audit Log screen in the Permit dashboard. The check appears with the user key, action, resource type, tenant, and decision.

  3. Repeat step 1 as a user without the permission. A second decision appears in the audit log, with Deny as the decision. NGINX still serves the request, which is the behavior the warning at the top of this page describes.

  4. If no decision appears, check the NGINX error log, usually /var/log/nginx/error.log.

Block denied requests

The auth_request module reads only the status code of the subrequest, and the PDP answers 200 for a denied check, so blocking needs a component that reads the allow field of the response body. Pick one:

ComponentHow it blocksWhen to use it
The ngx_http_js_module (njs)An njs handler calls the PDP, parses the JSON body, and returns 403 from the internal location when allow is false. See the njs documentation.You want the decision enforced in NGINX and can add the njs module
OpenResty with LuaA Lua block calls the PDP with ngx.location.capture or resty.http, reads allow, and calls ngx.exit(403).You already run OpenResty
The application behind NGINXThe service calls permit.check() before it handles the request. See Check permissions with permit.check().You control the service code and want object-level checks too

Both NGINX options call the PDP's /allowed endpoint, which takes the full check in the request body instead of headers. Run this request from the NGINX host to see the exact contract:

curl -X POST http://localhost:7766/allowed \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"user": { "key": "john@permit.io" },
"action": "read",
"resource": { "type": "document", "tenant": "default" }
}'

The response body holds the decision in the allow field, alongside a result copy of it for older SDKs and query and debug objects:

{
"allow": true,
"query": {},
"debug": {},
"result": true
}

A denied check returns the same HTTP 200 status with "allow": false. Your handler blocks the request when allow is false.

Return errors for blocked requests

Once a component returns 401 or 403 for the subrequest, auth_request fails the request with that status. Add these blocks to serve a response or a redirect for each status.

Return a response for unauthorized (401) requests:

error_page 401 = @error401;

location @error401 {
return 401 "Unauthorized";
# Or redirect to a login page:
# return 302 /login;
}

Return a response for forbidden (403) requests:

error_page 403 = @error403;

location @error403 {
return 403 "Forbidden";
# Or redirect to a custom error page:
# return 302 /error403;
}

Troubleshoot the integration

SymptomCauseFix
A path produces no checkThe location has no auth_request directiveAdd auth_request /auth; to every location that needs a check
NGINX returns 500 for every requestNGINX can't reach the PDP, or the PDP returned a status other than 2xx, 401, or 403Check that the PDP runs at the proxy_pass URL, and read the NGINX error log and the PDP container logs
No decision appears in the audit logThe subrequest didn't reach the PDP, or a permit-* header is missingCheck that the client or your authentication layer sends all four permit-* headers
The PDP answers 401The Authorization header in the /auth location has no valid environment API keyReplace <YOUR_API_KEY> with your environment API key
A denied check still reaches the applicationThe auth_request module reads only the status codeAdd one of the components in Block denied requests

Secure the PDP connection

  • Use HTTPS between clients and NGINX, and between NGINX and the PDP when they run on different hosts.
  • Allow connections to the PDP only from trusted sources, such as the NGINX server.
  • Anyone with your environment API key can change that environment's policy through the Permit API. Keep the NGINX configuration file that holds the key out of source control.

Monitor authorization overhead

NGINX sends one subrequest to the PDP for every request to a protected location. Monitor the latency of the subrequests, and run the PDP close to NGINX, for example on the same host, to keep that latency low.

Next steps