Nginx auth + rewrite gotcha

I came across a location a couple of days ago trying to understand why auth did not work:

location = /check/ {
satisfy all;
    allow x.x.x.x;
allow y.y.y.y;
deny all;
    auth_basic "Auth required";
auth_basic_user_file /etc/nginx/passwords/base;
    // do some stuff
    return 302 /userid/;
}

Basically what it does is checking whether a client is permitted to hit the location, “doing some stuff” and redirecting him to /userid/.

The location doesn’t have any syntax errors and it passes testing successfully, but there’s a thing… It does not work as expected.

If you add this location to nginx, you’ll find out that the check location simply ignores allow and auth_basic directives and always redirects you to /userid/.

Why’s that? Let’s dive deeper in how nginx handles requests.

How nginx handles a request

Nginx has multiple phases of request handling that are described in detail in the development guide. We can learn from it that rewrite phase happens before the access phase.

So our return directive belongs to rewrite module, allow and deny belong to access module and auth_basic is a part of auth module. Satisfy is defined in core module.

Now it makes more sense. It “resolves” the rewrite chain to the final destination and checks access only to it (not to any rewrites earlier) because that’s the location we wanna reach.

Workaround

A quick workaround for my specific case was:

location = /check/ {
satisfy all;
    allow x.x.x.x;
allow y.y.y.y;
deny all;
    auth_basic "Auth required";
auth_basic_user_file /etc/nginx/passwords/base;
    error_page 404 = @do_stuff;
}
location @do_stuff {
// do some stuff
return 302 /userid/;
}

Yes, that’s kinda ugly but lets us avoid refactoring the application behind nginx.