Considering that you are deploying a system - a system with no builtin authentication system, but it's crucial to you or a unexpected consequence would be made if used improperly. You want to add an extra layer of protection to it, but how?
The first thing that comes to my mind is basic_auth
of nginx. Docs
Generally speaking:
- make a password file with
htpasswd
fromapache2-utils
add
auth_basic "Realm name"; auth_basic_user_file /PATH/TO/HTPASSWORD/FILE;
to your existing profile. Either location or server.
And you're protected.
Pros:
- it's builtin with nginx, meaning it works out of box.
- it's universally supported.
- it's simple.
Cons:
- The authorization info expires once you closed your browser.
- Password is transferred plain, and you're limited to user/password authentication only.
Okay, so there is another way to protect your site with more controls, and that's auth_request
Docs
It's not built by default, so you have to include it manually with --with-http_auth_request_module
on compiling.
The principle of it is basically when any of the location protected was accessed, nginx sends a sub-request to the authorize location specified before actually processes the real request. If the sub request returns a non 200 response, the whole process halts, and you are capable of deciding which response and how the response is sent back to the client.
Say you have whole site to protect, and your original server config looks like this.
server {
listen 80;
server_name example.com;
root /usr/local/nginx/wwwroot/;
location / {
proxy_pass http://127.0.0.1:9080/;
proxy_buffering off;
}
}
First, let's add an auth_request
directive to protect the whole site, and let's assume it's at /restrict.
server {
listen 80;
server_name example.com;
root /usr/local/nginx/wwwroot/;
+ auth_request /restrict;
location / {
proxy_pass http://127.0.0.1:9080/;
proxy_buffering off;
}
+ location = /restrict {
+ internal;
+ proxy_pass http://127.0.0.1:4444/; # here runs my own authentication server
+ proxy_pass_request_body off; # no need to send the POST body
+
+ proxy_set_header Content-Length "";
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
}
In this example, only the url and header information are required to do the auth. You are free to do your own version.
Here is a demo authenticator I wrote. I will use it in this example.
In my demo, a basic auth will be returned if user is not authorized. Once the user entered the correct information, a one hour long token will be dispatch with the Set-Cookie
header. Once user is authorized, it is free of authorization for one hour, in spite of you closed your browser or not. And the token work all the way across the site.
And here's the problem:
Nginx discards all the headers returned from auth_request
handler, only the status code can be kept.
Luckily, there is an auth_request_set
directive that allows us to save any header returned from the handler to a local variable, and we can resend the header after.
So, here we go:
server {
listen 80;
server_name example.com;
root /usr/local/nginx/wwwroot/;
auth_request /restrict;
+ auth_request_set $saved_set_cookie $upstream_http_set_cookie; # save the cookie
location / {
proxy_pass http://127.0.0.1:9080/;
proxy_buffering off;
+ add_header Set-Cookie $saved_set_cookie; #resend the cookie
}
location = /restrict {
internal;
proxy_pass http://127.0.0.1:4444/; # here runs my own authentication server
proxy_pass_request_body off; # no need to send the POST body
proxy_set_header Content-Length "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
And one more thing.
If you're so unlucky like me and would like to do a 302 redirect on authentication success, you may have come up with a config like this:
server {
listen 80;
server_name example.com;
root /usr/local/nginx/wwwroot/;
auth_request /restrict;
auth_request_set $saved_set_cookie $upstream_http_set_cookie; # save the cookie
location / {
proxy_pass http://127.0.0.1:9080/;
proxy_buffering off;
add_header Set-Cookie $saved_set_cookie; #resend the cookie
+ return 302 http://example.com/newlocation; #redirect on success
}
location = /restrict {
internal;
proxy_pass http://127.0.0.1:4444/; # here runs my own authentication server
proxy_pass_request_body off; # no need to send the POST body
proxy_set_header Content-Length "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Unfortunately, it won't work. Nginx will always return 200 and completely ignores the auth_request.
This is because the return directive has a higher priority on config parsing. Nginx will always do the return directive if it is specified so that no auth will be done. The same thing goes truth for other authentication method as well, like basic_auth
.
The correct way to walkaround this is by using try_files with a non-exist file.
Here is our final result:
server {
listen 80;
server_name example.com;
root /usr/local/nginx/wwwroot/;
auth_request /restrict;
auth_request_set $saved_set_cookie $upstream_http_set_cookie; # save the cookie
location / {
proxy_pass http://127.0.0.1:9080/;
proxy_buffering off;
- add_header Set-Cookie $saved_set_cookie; #resend the cookie
+ try_files DUMMY @return302;
}
+ location @return302 {
+ add_header Set-Cookie $saved_set_cookie;
+ return 302 http://example/newlocation;
+ }
location = /restrict {
internal;
proxy_pass http://127.0.0.1:4444/; # here runs my own authentication server
proxy_pass_request_body off; # no need to send the POST body
proxy_set_header Content-Length "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Notice that, add_header
won't work in the / location block. It has to be in the @return302
block.
0 comment