Show a 404 with auth_request

The 404 error_page is using $errorsroot variable set by auth_request so that a different error page can be shown based on the user auth. For example if there’s no auth the 404 should be /html/noauth.html otherwise /html/yesauth.html.

location / {
  auth_request /auths
  auth_request_set $errorsroot $upstream_http_errorsroot;
  error_page 401 403 404 = @noauth;
  try_files $uri/index.html =404;
}

error_page 404 @errors404;
location @errors404 {
  root /html$errorsroot;
}

location = /auths {
  internal;
  proxy_pass http://your_auth_service;
  proxy_pass_request_body off;
  proxy_set_header Content-Length "";
}

It works for all pages that don’t exist except for when someone goes to /auths. It’s internal so I want to treat it like any other missing page if someone accesses it.

If someone goes to /nopage which doesn’t exist, auth_request is done first and the proper error_page will be loaded from root /html$errorsroot. If someone goes to /auths directly how can I do the same thing?

There’s the same problem with any location that is internal.

location /dontshow {
  internal;
}

If I load /dontshow/page it should be 404 and show the page from root /html$errorsroot; but it doesn’t.

Anyone know how to fix this?

Hi there,

your sample configs seem incomplete; but when I try to adapt them and mock the missing parts, it looks like it all Works For Me? (At least when the /auths subrequest returns the already-auth-or-not status in the response header.)

I think that the important part is that in your samples, $errorsroot is only set for an incoming request that was handled in “location / {}”, but you want it to take effect for all incoming requests. So – have the auth_request and auth_request_set directives at server{} level, outside all location{}s.

With that change, and adjusting:

location @errors404 {
  try_files $errorsroot =404;
}

then I get the content of html/yesauth.html or html/noauth.html for any 404 response, where the upstream auth response headers include something like

errorsroot: /noauth.html

in its http-200 response.

You will probably want to adjust the @noauth piece to not catch 404; I did not test that side of things.

If you make those two changes, do your tests show what you want to see?

(Note that with auth_request at server{} level, the url is likely to be called multiple times for each client request. That should hopefully be ok if it is a lightweight “is this request already authenticated?” check. It may be messy if it will lead to an authentication challenge to the client each time.)

Cheers,

Hi @xlf, @FrancisD is right on both counts.

Refer to this configuration.

server {
    # Hoist auth to server scope so $errorsroot is
    # populated for every request that reaches the ACCESS phase, including
    # requests that later internal-redirect into a named error location.
    auth_request      /auths;
    auth_request_set  $errorsroot $upstream_http_errorsroot;

    # Declare BOTH error_page mappings in the SAME block, since
    # error_pages inheritance is whole-array replacement, not a merge.
    # Splitting them across server{} and
    # location{} silently discards the server-level entry.
    #
    # Pick ONE target per status code. If 401/403 need @noauth and 404
    # needs @errors404, they must be declared together:
    error_page 401 403 = @noauth;
    error_page 404       @errors404;   # NO "=" -- see note below

    location / {
        # NOTE: do NOT declare any error_page here, or the server-level
        # list above is discarded for this location.
        try_files $uri/index.html =404;
    }

    # try_files installs a real content handler and takes an
    # explicit variable-expanded path. `root` alone would install no
    # handler and would map the ORIGINAL request URI, not $errorsroot
    # (ngx_http_core_content_phase() → "no handler found" → 404).
    location @errors404 {
        root      /html;
        try_files $errorsroot =404;
    }

    location @noauth {
        root      /html;
        try_files /noauth.html =404;
    }

    location = /auths {
        internal;
        proxy_pass               http://your_auth_service;
        proxy_pass_request_body  off;
        proxy_set_header         Content-Length "";
    }
}

On the /auths-direct-request question specifically: with the above, a direct client request to /auths returns a generic 404 — the error_page 404 = @errors404; mapping does fire, and @errors404 is entered — but $errorsroot is empty, so try_files $errorsroot =404; falls through to the =404 fallback and the built-in error page is served. If a deterministic page is required for this case, add an explicit fallback rather than relying on the auth-derived variable:

    location @errors404 {
        root /html;
        # $errorsroot is empty for requests that never reached the ACCESS
        # phase (e.g. direct hits on `internal` locations). Fall back
        # explicitly rather than to the built-in error page.
        try_files $errorsroot /noauth.html =404;
    }

This is the closest achievable behaviour to the reporter’s stated goal (“treat it like any other missing page”), and it fails closed to the unauthenticated page — the correct security default, since a request that never passed auth should not be shown the authenticated variant.