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 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
-
A client request reaches an NGINX
locationthat has anauth_requestdirective. -
NGINX sends a subrequest to an internal location, which proxies it to the PDP's
/nginx_allowedendpoint. -
The PDP reads four request headers and runs a permission check:
Header Check field permit-user-keyUser key permit-actionAction permit-resource-typeResource type permit-tenant-idTenant -
The PDP returns HTTP
200with a JSON body whoseallowfield istrueorfalse, and records the decision in the Permit audit log. -
NGINX reads the status code of the subrequest and forwards the request. See the ngx_http_auth_request_module documentation.
Prerequisites
- NGINX built with
ngx_http_auth_request_module - A Permit.io policy with the resources, actions, and tenants your requests use (Configure your first RBAC policy)
- A running PDP that NGINX can reach, for example at
http://localhost:7766(Run the PDP) - Your environment API key (Get your API key)
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 withauth_request /auth. - The internal
location = /authblock proxies the subrequest to the PDP's/nginx_allowedendpoint as aPOSTrequest without a body, adds your environment API key (replace<YOUR_API_KEY>) in theAuthorizationheader, and copies the fourpermit-*headers from the client request. The PDP answers401for a request without theAuthorizationheader.
$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
-
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/ -
Open the Audit Log screen in the Permit dashboard. The check appears with the user key, action, resource type, tenant, and decision.
-
Repeat step 1 as a user without the permission. A second decision appears in the audit log, with
Denyas the decision. NGINX still serves the request, which is the behavior the warning at the top of this page describes. -
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:
| Component | How it blocks | When 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 Lua | A 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 NGINX | The 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
| Symptom | Cause | Fix |
|---|---|---|
| A path produces no check | The location has no auth_request directive | Add auth_request /auth; to every location that needs a check |
NGINX returns 500 for every request | NGINX can't reach the PDP, or the PDP returned a status other than 2xx, 401, or 403 | Check 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 log | The subrequest didn't reach the PDP, or a permit-* header is missing | Check that the client or your authentication layer sends all four permit-* headers |
The PDP answers 401 | The Authorization header in the /auth location has no valid environment API key | Replace <YOUR_API_KEY> with your environment API key |
| A denied check still reaches the application | The auth_request module reads only the status code | Add 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.