Reputation: 423
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.
Upvotes: 9
Views: 8040
Reputation: 31
Maybe it's too late. But I've found another solution. Caching auth_request with proxy_cache.
# Working config for auth_request with proxy_cache. (nginx 1.18.0)
# https://gist.github.com/jinto/f120e497db8d39d866db49ff2454b7b3
# ...
proxy_cache_path /tmp/cache_xx levels=1:2 keys_zone=auth_cache:10m max_size=128m inactive=10m use_temp_path=off;
server {
location / {
auth_request /_ml_proxy/auth;
# ...
proxy_pass http://backend_ip:7860/;
}
location ~ ^/_ml_proxy/auth { internal;
include proxy_params;
proxy_cache auth_cache;
proxy_cache_methods GET HEAD POST;
proxy_cache_key $cookie_sessionid; # for django
proxy_cache_valid 200 1m;
proxy_pass http://backend_auth_ip;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
}
}
Upvotes: 1
Reputation: 425
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.
Upvotes: 3
Reputation: 39
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.
Upvotes: 1