Document issues using `if` and `return` with headers
pgthompson24 opened this issue · 10 comments
Hello, I am currently looking to only allow a particular user (JWT subject) to access a specific endpoint on my web server. So I am using the following configuration to do so:
location /endpoint {
proxy_set_header Host $http_host;
auth_jwt_enabled on;
auth_jwt_algorithm RS384;
auth_jwt_validate_sub on;
auth_jwt_extract_request_claims sub;
if ($http_jwt_sub != "super-user") {
return 401 [$http_jwt_sub];
}
auth_jwt_use_keyfile on;
auth_jwt_keyfile_path "<mysecretlocation>";
auth_jwt_location COOKIE=token;
proxy_pass http://localhost:3000;
}
This configuration works without the bit where I try to validate the claims. It even allows access with the auth_jwt_validate_sub on;
config. It validates the sub
exists but my page yields empty brackets []
on return (i.e. the $http_jwt_sub variable is empty). I have tested and found that it fails to extract any values for other parameters of my JWT payload as well. And I can confirm that my JWT does in fact contain these fields:
Has anyone else experienced this or is there some syntax I am not following properly?
Can you confirm which version of NGINX you're using, please?
@JoshMcCullough The system is using NGINX version 1.21.1.
In our tests, we ended up using a header to store the value of the extracted claim(s) because we, too, noticed that return
does not seem to interpolate the variable. Although the documentation states that it does, and the code agrees.
All we are doing in the module is pulling the claim out of the JWT and storing it in the reqeuest and/or response headers. We format the name of the header as JWT_<claim_name
e.g. JWT_sub
, but headers are treated as case-insensitive, so we access the value of the header as $http_jwt_sub
. I tried also changing our prefix to jwt_
(lower case) but it did not fix the problem.
This works when writing the claim value to response header:
add_header "Test" "sub=$http_jwt_sub";
But not when using return
:
return 200 "test ... $http_jwt_sub ... ";
Nor when using set
:
set $sub $http_jwt_sub;
return 200 "test ... $sub ... ";
In fact, when using return
at all, it seems that any headers set are not available:
add_header "Test" "sub=$http_jwt_sub";
return 200 "test ... $http_jwt_sub ...";
Outputs:
< HTTP/1.1 200 OK
< Server: nginx/1.24.0
< Date: Fri, 09 Jun 2023 16:01:38 GMT
< Content-Type: application/octet-stream
< Content-Length: 13
< Connection: keep-alive
< Test: sub=
<
{ [13 bytes data]
- Connection #0 to host nginx left intact
test ... ...
This may be a case of the rewrite module overwriting the headers, as is seen when you use add_header
at a higher level (e.g. in the server
block) and then again at a lower level (e.g. in a location
block) -- the headers array are not "merged", they are overwritten. So when using return
, this seems to be what is happening but I haven't verified it via code.
Also, it is not generally recommended to use if
. I wonder if you can re-work your use case to use headers instead of return
, and not use an if
(use a map instead)?
Thanks for the response. In my case, I don't actually need to return the value of sub
I was just attempting to return it for debugging purposes. Considering your advice, I can't think of a way to conditionally return 401
without using an if
statement. So I attempted to evaluate the http_jwt_sub
variable using a map
:
http {
auth_jwt_algorithm RS384;
auth_jwt_use_keyfile on;
auth_jwt_keyfile_path "<mysecretlocation>";
auth_jwt_location COOKIE=token;
auth_jwt_extract_request_claims sub;
map $http_jwt_sub $valid {
"super-user" 1;
default 0;
}
server {
listen 8080;
error_log /opt/data/NGINX.log debug;
client_max_body_size 1G;
location /endpoint{
auth_jwt_enabled on;
proxy_set_header Host $http_host;
if ($valid = 0) {
return 401 "Unauthorized user";
}
proxy_pass http://localhost:3000;
}
But, perhaps unsurprisingly, the $valid
variable appears to default to 0 when it should be 1. Is there a different approach I can take to evaluate the $http_jwt_sub
variable that subverts having to use any of these operators that seem to handle it improperly? Apologies in advance as my NGINX knowledge is somewhat limited.
Yeah, there's no need to use map
since you are using if
anyway. So reverting to your original code should work.
In my original code, the if
statement does not appear to properly evaluate the $http_jwt_sub
variable.
Ah, sorry I misunderstood that. I've been messing around with this and I don't know if it's a bug or not, but it seems like as soon as you use return
, you can't access and request or response headers. I tried altering our JWT module code but cannot get this to work. Even when you do add_header Test abc123
and then immediately return 200 test=$http_test
, the header is not read / returned.
I don't have an answer for you currently. But I will backtrack again on my if
comment because they way you're using it is how it was intended to be used.
I'll continue looking for a solution and will report back if I can find something...
FWIW I did find a workaround for my issue. The service at my target endpoint supports role based access so I used a map on the $http_jwt_sub
variable like so:
http {
auth_jwt_algorithm RS384;
auth_jwt_use_keyfile on;
auth_jwt_keyfile_path "<pubkeypath>";
auth_jwt_location COOKIE=token;
auth_jwt_extract_request_claims sub;
map $http_jwt_sub $user {
"super-user" "admin";
default "notadmin";
}
server {
listen 8080;
client_max_body_size 1G;
# Grafana endpoint
# Authenticate using the JWT that gets stored in the `token` cookie when we log in to the local UI
location /enpoint {
auth_jwt_enabled on;
proxy_set_header Host $http_host;
proxy_set_header X-WEBAUTH-USER $user;
proxy_pass http://localhost:3000;
}
And the $http_jwt_sub
variable was successfully interpreted by the map
.
Avoiding if
and return
seems like the way to go in a case like this.
Excellent! I'll close this issue as there's nothing for us to do.
Actually, we can use this ticket to add some docs regarding this.