Discussion:
[PATCH] Allow http_auth_request_module to forward 302 responses
Maxim Khitrov
2012-02-18 14:19:18 UTC
Permalink
Hello Maxim,

The attached patch allows your http_auth_request_module to forward a
302 response and the associated "Location" header to the client. The
goal is to allow the authentication back end to redirect the client to
a login page instead of using WWW-Authenticate header.

I'm currently attempting to use your module to authenticate users
against an Active Directory server. I have a PHP script that can
perform the necessary security checks and cache user credentials for
better performance. The problem is that if I rely on HTTP Basic
authentication, I lose control over the client's session (timeout,
logout, etc.). I know that it is possible to force some browsers to
"forget" the credentials in order to log out, but it's a hack that I'd
rather avoid.

The best solution is to use cookies, but for this I need to be able to
redirect the user to the login page when authentication fails. The
current behavior of the auth_request module is to return an Internal
Server Error for any response code other than 401, 403, or 200.

To make this patch, I simply copied your handling of the
www_authenticate header. If there is a more elegant solution or some
additional logic required, please feel free to change the code as
needed.

- Max
-------------- next part --------------
A non-text attachment was scrubbed...
Name: ngx_http_auth_request_module-location.patch
Type: application/octet-stream
Size: 890 bytes
Desc: not available
URL: <http://mailman.nginx.org/pipermail/nginx-devel/attachments/20120218/6e0c3c43/attachment.obj>
Maxim Dounin
2012-02-18 21:45:42 UTC
Permalink
Hello!
Post by Maxim Khitrov
Hello Maxim,
The attached patch allows your http_auth_request_module to forward a
302 response and the associated "Location" header to the client. The
goal is to allow the authentication back end to redirect the client to
a login page instead of using WWW-Authenticate header.
You may get the same result by returning 401/403 and using
appropriate error_page handler. I don't actually see a reason to
handle 302 specially here.

Something like this should work (not tested):

location / {
auth_request /auth;
auth_request_set $auth_redirect $upstream_http_location;
error_page 401 = /auth_redirect;
}

location /auth {
proxy_pass http://auth_backend;
...
}

location /auth_redirect {
return 302 $auth_redirect;
}

Maxim Dounin
Maxim Khitrov
2012-02-18 23:48:50 UTC
Permalink
Post by Maxim Dounin
Hello!
Post by Maxim Khitrov
Hello Maxim,
The attached patch allows your http_auth_request_module to forward a
302 response and the associated "Location" header to the client. The
goal is to allow the authentication back end to redirect the client to
a login page instead of using WWW-Authenticate header.
You may get the same result by returning 401/403 and using
appropriate error_page handler. ?I don't actually see a reason to
handle 302 specially here.
Allow me to clarify. I cannot use 401/403 codes, because I need both
of those to perform their original function. 403 has to block access
without redirecting anywhere and 401 must be used to pass the
WWW-Authenticate header.

The latter may seem odd, given that I'm using cookies, but I also need
the ability to "downgrade" to HTTP Basic authentication for clients
that cannot handle cookies properly. I do this by maintaining a
whitelist of clients that are known not to support cookies (e.g.
mercurial or various scripted clients). When my authentication
back-end sees a matching User Agent string, it automatically allows
the use of Basic authentication by sending a 401 response. Everyone
else will be either denied access or be redirected to provide their
credentials via an html form.

Even without this scheme, I think allowing the use of a 302 response
would be a useful feature. Relying on the error_page configuration,
which I did consider before making my patch, increases the complexity
of nginx.conf and could lead to unintended behavior. This way, a
single "auth_request /auth;" line takes care of all possible decisions
(authenticate/allow/deny/redirect).

- Max
Maxim Dounin
2012-02-19 04:00:19 UTC
Permalink
Hello!
Post by Maxim Khitrov
Post by Maxim Dounin
Hello!
Post by Maxim Khitrov
Hello Maxim,
The attached patch allows your http_auth_request_module to forward a
302 response and the associated "Location" header to the client. The
goal is to allow the authentication back end to redirect the client to
a login page instead of using WWW-Authenticate header.
You may get the same result by returning 401/403 and using
appropriate error_page handler. ?I don't actually see a reason to
handle 302 specially here.
Allow me to clarify. I cannot use 401/403 codes, because I need both
of those to perform their original function. 403 has to block access
without redirecting anywhere and 401 must be used to pass the
WWW-Authenticate header.
Actually you can. You just need to use some additional processing
inside error_page handler (or two different error_page handlers,
selected based on auth request answer), e.g.

location / {
auth_request /auth;
auth_request_set $auth_redirect $upstream_http_location;
error_page 403 = /auth_403;
...
}

location = /auth {
proxy_pass http://auth_backend;
...
}

location = /auth_403 {
if ($auth_redirect) {
return 302 $auth_redirect;
}

return 403;
}

And then either set Location (or some other arbitrary header) in a
403 response or not.

[...]
Post by Maxim Khitrov
Even without this scheme, I think allowing the use of a 302 response
would be a useful feature. Relying on the error_page configuration,
which I did consider before making my patch, increases the complexity
of nginx.conf and could lead to unintended behavior. This way, a
single "auth_request /auth;" line takes care of all possible decisions
(authenticate/allow/deny/redirect).
I believe the problem here is how one define "all possible
decisions". And I'm really sure that if 302 will be allowed - 303
and 307 will appear next to it, and then we'll start discussing
which headers should be passed - e.g. Set-Cookie looks like
something required in addition to Location. That's why I
(intentionally) limited it to only handle 401/403, much like
normal auth_basic.

I think correct aproach for the future would be to implement some
"transparent" mode, much like fastcgi authorizers, where one may
return arbitrary answer with any headers and body while rejecting
request. This is not something trivial to implement as of now
though, mostly because of body.

Maxim Dounin
Maxim Khitrov
2012-02-19 13:40:45 UTC
Permalink
Post by Maxim Dounin
Hello!
Post by Maxim Khitrov
Post by Maxim Dounin
Hello!
Post by Maxim Khitrov
Hello Maxim,
The attached patch allows your http_auth_request_module to forward a
302 response and the associated "Location" header to the client. The
goal is to allow the authentication back end to redirect the client to
a login page instead of using WWW-Authenticate header.
You may get the same result by returning 401/403 and using
appropriate error_page handler. ?I don't actually see a reason to
handle 302 specially here.
Allow me to clarify. I cannot use 401/403 codes, because I need both
of those to perform their original function. 403 has to block access
without redirecting anywhere and 401 must be used to pass the
WWW-Authenticate header.
Actually you can. ?You just need to use some additional processing
inside error_page handler (or two different error_page handlers,
selected based on auth request answer), e.g.
? ?location / {
? ? ? ?auth_request /auth;
? ? ? ?auth_request_set $auth_redirect $upstream_http_location;
? ? ? ?error_page 403 = /auth_403;
? ? ? ?...
? ?}
? ?location = /auth {
? ? ? ?proxy_pass http://auth_backend;
? ? ? ?...
? ?}
? ?location = /auth_403 {
? ? ? ?if ($auth_redirect) {
? ? ? ? ? ?return 302 $auth_redirect;
? ? ? ?}
? ? ? ?return 403;
? ?}
And then either set Location (or some other arbitrary header) in a
403 response or not.
Thanks, I'll use this, but I really don't like this method. It makes
testing difficult, because when you call the authentication script
directly, which you have to do during the development, you are no
longer receiving normal responses. You get a 403 status and a Location
header that you must now enter manually. Maybe there is some other way
of getting nginx to act on the location, but it just feels like one
hack on top of another.
Post by Maxim Dounin
[...]
Post by Maxim Khitrov
Even without this scheme, I think allowing the use of a 302 response
would be a useful feature. Relying on the error_page configuration,
which I did consider before making my patch, increases the complexity
of nginx.conf and could lead to unintended behavior. This way, a
single "auth_request /auth;" line takes care of all possible decisions
(authenticate/allow/deny/redirect).
I believe the problem here is how one define "all possible
decisions". And I'm really sure that if 302 will be allowed - 303
and 307 will appear next to it, and then we'll start discussing
which headers should be passed - e.g. Set-Cookie looks like
something required in addition to Location. That's why I
(intentionally) limited it to only handle 401/403, much like
normal auth_basic.
Add two extra checks to the outer 'if' statement and 303/307 codes are
handled :)

In practice, most people will not use these due to the uncertainty in
client support and the fact that languages like PHP default to sending
a 302 response when the Location header is set.

As for auth_basic, I don't think this is a fair comparison. What other
things could auth_basic do? It only deals with a single header, which
can easily be handled in the context of the current request. Your
auth_request module could allow for much more flexibility.
Post by Maxim Dounin
I think correct aproach for the future would be to implement some
"transparent" mode, much like fastcgi authorizers, where one may
return arbitrary answer with any headers and body while rejecting
request. This is not something trivial to implement as of now
though, mostly because of body.
Wouldn't redirects take care of this already? You don't need the
authentication script to be the source of extra headers or some html
content. You just need to give it a way of rejecting the request and
pointing the client to a dedicated login page that takes care of
everything else. Your solution with error pages works, but I still
think that allowing redirects would be cleaner.

All you are really gaining with the transparent mode is the ability to
keep the same URL and avoid a redirect for the client. Is the increase
in complexity really worth it?

In any case, thanks for sharing your thoughts on the topic. I'll play
with error_page configuration and see how well I can make it work for
my needs.

- Max

Loading...