2
votes

I have been using NGINX as a reverse proxy and recently decided I needed to add authentication to a route on a website. I realised that there area multiple NGINX modules that could allow me to handle the authentication through NGINX (see links below). So, I build a single-sing-on login page that integrates auth0 to test how this would work.

All modules are similar and allow you to specify an auth_jwt_key (for validation of the JWT) as well as a variable to where the JWT (auth_jwt) is stored. I decided to store the JWT in a cookie.

Unfortunately, I can not get the validation through NGINX to work and keep seeing a 401 unauthorized return code.

Here is the part of the flask app, where I am handling the auth0 login and storing the JWT into a cookie:

@app.route('/callback')
    def callback_handling():
    token = auth0.authorize_access_token()
    response = redirect('/dashboard')
    response.set_cookie('lt_jwt', value=token.get('id_token'), max_age=token.get('expires_in'))
    return response

Can you see something wrong with this? The resulting cookie looks something like this (I removed most of it for readability): lt_jwt eyJ0eX[...]SkRNZyJ9.eyJnaXZ[...]kzNDV9.d3Tzr[...]NzbA staging-auth0-login.scapp.io / 9/13/2018, 1:55:45 PM 1.03 KB

I can put the cookie value into jwt.io and decode it properly, but my problem is that the mentioned NGINX modules have issues decoding it.

Here is an example NGINX config, where I am setting up the authentication:

location = /dashboard {
    auth_jwt_key "AUTH0_CLIENT_SECRET";
    auth_jwt $cookie_lt_jwt;

    root /usr/src/lt/nginx;
    try_files /dashboard.html =404;
}

Basically, I never get to see dashboard.html and always get the 401 unauthorized. The NGINX error.log shows that decoding the JWT failed: [warn] 64#64: *23 JWT: failed to parse jwt, which in this case is an error log from the custom nginx module I used:

// Validate the jwt
if (jwt_decode(&jwt, jwt_data, conf->jwt_key.data, conf->jwt_key.len))
{
  ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "JWT: failed to parse jwt");
  return NGX_HTTP_UNAUTHORIZED;
}

Reference: https://github.com/maxx-t/nginx-jwt-module/blob/d9a2ece81ca66647f81fc2586b29b348af67f8aa/src/ngx_http_auth_jwt_module.c#L124

Unfortunately, it's not super easy to debug it, but I received a similar response from another custom NGINX module that I also tested: jwt_verify: error on decode: SUCCESS, which is a result of this code segment:

  jwt_t* token;
  int err = jwt_decode(&token, token_data, alcf->key.data, alcf->key.len);
  if (err) {
    ngx_log_error(NGX_LOG_ERR, r->connection->log, errno,
                  "jwt_verify: error on decode: %s", strerror(errno));
    return ngx_http_auth_jwt_set_realm(r, &alcf->realm);
  }

Reference: https://github.com/tizpuppi/ngx_http_auth_jwt_module/blob/bf8ae5fd4b8e981b7683990378356181dee93842/ngx_http_auth_jwt_module.c#L247

So in both cases jwt_decode is called and fails (even though it's error code apparently is SUCCESS).

The reason I am asking here is that I feel I might be doing something conceptually wrong here:

  1. formatting the cookie (I have seen jwt cookies looking like this: Bearer eyJ0eX[...]SkRNZyJ9.eyJnaXZ[...]kzNDV9.d3Tzr[...]NzbA
  2. assuming that an auth0 generated token can be used this way
  3. ...?

Please let me know if you have any insight or good working example with NGINX + auth0. I have read this article https://auth0.com/blog/use-nginx-plus-and-auth0-to-authenticate-api-clients/ by auth0 detailing how a very similar NGINX Pro module could be used, but I don't have access to that commercial module.

1

1 Answers

1
votes

To answer the main question: The cookie should really contain only the JWT without any prefix. The python (flask) code in the question isn’t doing anything wrong and I was able to confirm with curl that you can in fact get authorized (with a valid JWT) stored in a cookie:

curl https://some.page.com/application --cookie "lt_jwt=eyJ0eX[...]SkRNZyJ9.eyJnaXZ[...]kzNDV9.d3Tzr[...]NzbA"

So what is the problem here - why can’t I authenticate in the browser, when it works via curl?

The reason was in the NGINX config, which required authentication to be disabled in the http context. This was essentially a bug in the NGINX module, which the developer managed to fix since.

Lesson learned: it's usually not your fault ;)