I want to have my nginx proxy perform a subrequest for authentication only if the client is not already authenticated. The conditional part is where I am stuck. How can I craft a configuration so that the client is only authenticated once per session?
I am able to successfully perform an auth_request to Apache and pull back the headers I want to pass on to the back-end, but this is occurring on every request and is expensive.
In the example here, my goal is to only perform the auth_request if the "Authorization" header is missing or empty or alternately a cookie containing the token
# DEFAULT BACKEND
location / {
proxy_pass_request_body off;
if ($http_authorization ~* '')
{
rewrite ^(.*)$ /__login;
}
if ($user !~* "([aa-zZ]+)@example.com")
{
}
if ($http_cookie !~* "(auth_cookie=([aa-zZ]+)@example.com)")
{
add_header Set-Cookie "auth_cookie=$user;domain=.example.com;Max-Age=3000";
}
proxy_pass_header x-webauth-user;
proxy_pass_header Set-Cookie;
proxy_pass http://example.com:6762/;
}
location /__login { internal;
auth_request /auth;
auth_request_set $user $upstream_http_x_webauth_user;
set $xuser $user;
add_header Auth-User $user;
proxy_set_header User-Name $user;
proxy_set_header Authorization $http_authorization;
#proxy_pass_header x-webauth-user;
#proxy_pass_header Set-Cookie;
proxy_pass http://example:6762/;
access_log /etc/nginx/login_debug.log;
}
location = /auth{
internal;
proxy_pass http://example.com:81/;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-for $proxy_add_x_forwarded_for;
#proxy_pass_header Set-Cookie;
#proxy_pass_header x-webauth-user;
}
The Auth-User header gets lost on all requests after the first and the cookie never seems to get set, beyond that the page doesn't actually seem to render in a browser. I am obviously doing something very wrong, could some please help me figure this out.
Please check out the NJS (https://nginx.org/en/docs/njs/) module. It's really simple and for sure can do what you want. Here is the example solution:
file: /etc/nginx/conf.d/default.conf:
server {
listen 80;
server_name "SOME_SERVER";
# make an authentication subrequest for every request
auth_request /auth;
# create a new variable AuthToken and set its value to the res.SOMEVALUE from the later subrequest...
auth_request_set $AuthToken $sent_http_token;
# add new AuthToken to the request
proxy_set_header Authorization $AuthToken;
location / {
proxy_pass http://SOME_ENDPOINT;
}
location = /auth {
internal;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
js_content auth.main;
}
location /get-new-token-location {
internal;
proxy_pass http://SOMEURL;
}
}
and the example of nginx.conf file to show how to enable the NJS module:
...
pid /var/run/nginx.pid;
load_module /usr/lib/nginx/modules/ngx_http_js_module.so;
events {
use epoll;
worker_connections 10000;
}
http {
# import njs scripts
js_import auth from /path/to/the/auth.js;
include /etc/nginx/conf.d/default.conf;
}
and finally, the main function from auth.js file:
export default {main}
function main(r) {
var token = "";
// search token in Authorization header
if (this.requestHeaderExists(r, 'Authorization')) {
var m = r.headersIn.Authorization.match(/Bearer\s+(.+)/);
if (m !== null && typeof m[1] !== 'undefined') {
token = m[1];
}
}
// search token in cookie
if (token.length == 0) {
... code here ...
}
// token was found, you can somehow validate it if you want
if (token.length > 0) {
.., make sure token is valid...
}
else { // there is no token, so ask for the new one
r.subrequest('/get-new-token-location, { method: 'GET' }, function(reply) {
var res = JSON.parse(reply.responseBody);
// add token to the response headers of this sub-request
r.headersOut['token'] = res.SOMEVALUE;
}
}
r.return(200);
return;
}
Please treat it as an example. Ok, maybe it looks complicated, but it is really powerful and for sure you can find more examples in the world wide web.
The Nginx wiki warns that if
inside location
may give unexpected results, but that rewrite ... last;
is safe. Here is an example:
location / {
if ($cookie_UserName = "") {
rewrite ^ /__login$uri last;
}
proxy_pass http://backend-app;
}
location /__login { internal;
rewrite ^/__login(?<realurl>/.*)$ $realurl break;
auth_request /auth;
auth_request_set $user $upstream_http_x_webauth_user;
proxy_set_header Cookie UserName=$user;
proxy_pass http://backend-app;
add_header Set-Cookie "UserName=$user;Max-Age=300";
}
location = /auth { internal;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_pass http://auth-server/validate;
}
There are two cases: Cookie:UserName exists or not. If it exists the first proxy_pass
is executed. Otherwise /__login
is used. Note that $uri
is passed, so that it can be sent to backend-app.
For more advanced conditionals, you may use map instead of if
.
Beware, though, that not authenticating every request runs the risk of accepting requests with a "faked" cookie/header.
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