tl:dr, if a request is redirected using a nginx error_page directive, how can I make sure that a custom-header is passed with this redirect.
I'm currently implementing a system where nginx authenticates against an LDAP server an acts as a reverse-proxy. It's based on this https://www.nginx.com/blog/nginx-plus-authenticate-users/ The way it works is that, it has a all encompassing server block, that sends the request to an authentication page running on another port, this sends back either a 403 or a 200 based on the contents of a cookie. If this is a 403, the user gets sent to a login page, if 200 on to their destination.
The example nginx conf is here:
location / {
auth_request /auth-proxy;
# redirect 401 to login form
error_page 401 =200 /login;
proxy_pass http://backend/;
}
location /login {
proxy_pass http://backend/login;
# Login service returns a redirect to the original URI
# and sets the cookie for the ldap-auth daemon
proxy_set_header X-Target $request_uri;
}
location = /auth-proxy {
internal;
# The ldap-auth daemon listens on port 8888, as set
# in nginx-ldap-auth-daemon.py.
# Change the IP address if the daemon is not running on
# the same host as NGINX/NGINX Plus.
proxy_pass http://127.0.0.1:8888;
}
The problem is, I've made an alteration that makes the authentication part time out and return a 403 code, regardless of whether the cookie is valid or not. However I want to let the user know this has happened on the login page, so the python authentication code sets X-authenticationfail header with the error value in it.
I've checked this works by using curl to send a timed out cookie to the authentication code, and read the header back. However this header has disappeared when the login python code is called. I think this is due to nginx not forwarding it. I've tried to include the lines "proxy_set_header X-authenticationfail $http_x_authenticationfail;", "add_header X-authenticationfail "test" always;" before the error_page directive, but this has no effect. I've also tried "proxy_pass_header X-authenticationfail" in the same location but this also failed.
The server block has "underscores_in_headers on" set, and "proxy_pass_request_headers on" set. In the login block, placing "proxy_set_header X-authenticationfail "testvalue";" got the value to the login python code and from there back to the browser.
Thanks for getting this far!
From your description, the task is to preserve a header from the auth_request
response in one location and then pass this header to the next upstream in another location.
A header from any response can be reached with $sent_http_<header name>
variable within the location from which the request is made. The problem here is that Nginx is very memory efficient. If you somehow manage to escape a location, that is, make an internal redirect, Nginx decides that you are about to make another request, serve a static file or do something else. So any data from the previous request is now considered irrelevant and is dismissed in order to free some memory. In other words, $sent_http_<header name>
variables are attached to a location and they stop working as soon as you leave that location.
However, there is a way to preserve some of these variables by making another reference to the area of memory holding the value of interest. This is possible, because, as was noted earlier, Nginx is memory efficient. If you assign a variable with the value of another varialbe, the web server will not copy the value of the source variable unless it is really necessary. Instead it will set the internal pointer of the new variable with the address in memory, where the value of the source variable is kept.
So if you assign a new variable with the value of $sent_http_<header name>
, there will be two references to the area of memory where the response header is stored. And your new variable will keep this area of memory from being wiped, because, unlike most of the built-in variables which work only within a specific location, user-defined variables are attached to the server context.
So in order to solve your problem you will need to define a new variable and assign it with the value of $sent_http_x_authenticationfail
.
The first obvious choice would be to use the set
directive for that. However it will not work, because set
is called at a very early stage of the request processing, before any request is sent and any response is recieved.
Luckily, there is a special directive in Nginx, that allows exactly that — assigning a new variable with the results of auth_request
. And this directive is auth_request_set. Combining it with proxy_set_header
you will get what you want:
location / {
auth_request /auth-proxy;
# Step 1: force Nginx to preserve the response header
auth_request_set $falure_reason $sent_http_x_authenticationfail;
error_page 401 =200 /login;
proxy_pass http://backend/;
}
location /login {
proxy_pass http://backend/login;
proxy_set_header X-Target $request_uri;
# Step 2: pass the preserved header value to the next upstream
proxy_set_header X-Authenticationfail $sent_http_x_authenticationfail;
}
location = /auth-proxy {
internal;
proxy_pass http://127.0.0.1:8888;
}
Note that since both $falure_reason
and $sent_http_x_authenticationfail
now point to the same area of memory, you can use any of these variables in your login
location with the same result.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With