lua-resty-openidc icon indicating copy to clipboard operation
lua-resty-openidc copied to clipboard

Authentication with Keycloak in Lua makes redirection loops

Open ThihaM2G opened this issue 3 years ago • 14 comments

Hi,

First, I would like to explain my current setup. Nginx is stalled in both Server-1 and Server-2. Server-1 is the main reverse proxy. Server-2 is where the Lua script (authentication with Keycloak) exists. So we can say Server-1 is sitting in front of Server-2. When we access server-1.com/app/download/..., the request is passed to server-2.com/app/download/.... This is where the Lua script starts working. The idea is to authenticate the user with Keycloak. If authentication succeeds, Server-2 will authenticate with DigitalOcean Spaces to download the file. That's not too complex, right?

Expected behaviour
  • access to server-1.com/app/download/...
  • show Keycloak login page
  • after logging in with the correct credentials, a file will be downloaded
Actual behaviour
  • access to server-1.com/app/download/...
  • show Keycloak login page
  • after logging in with the correct credentials, the redirection loop starts
Environment
  • lua-resty-openidc version: 1.7.5-1
  • OpenID Connect provider: Keycloak

Before I provide my Nginx config file, please let me ask a few quick questions. Does lua-resty-openidc authentication with Keycloak work well for the above setup? From the comment, @bodewig mentioned having another reverse proxy sitting in front of the Nginx running the authentication proxy code can be the problem. It seems similar to my case. Please let me know your opinions. Thank you!

Here is the snippet of my Nginx config file located in Server-2
location ~* /app/download/(.*) {
  set $uri_full '$1';
  access_by_lua_block {
    local opts = {
      redirect_uri_path = "/app/download/" .. ngx.var.uri_full,
      -- I think complete URL becomes: server-1.com/app/download/test.pdf, file name is dynamic!
      discovery = "https://server-1.com/auth/realms/test/.well-known/openid-configuration",
      client_id = "...",
      client_secret = "...",
      redirect_uri_scheme = "https",
      session_contents = {id_token=true, user=true},
      ...
    }
    local res, err = require("resty.openidc").authenticate(opts)
  }
}
About redirection loop
  • access to server-1.com/app/download/...
  • Server-1 Nginx receives the request and transfers it to Server-2
  • Server-2 Nginx catches it and executes the Lua script
  • the Lua script authenticates with Keycloak and makes redirection based on redirect_uri_path (Note: redirect_uri_path is server-1.com/app/download/...)
  • Server-1 Nginx receives the request and transfers it to Server-2 again
  • Server-2 Nginx catches it and executes the Lua script again
  • it's looping that way, the Lua script never knows the authentication with Keycloak is already done

ThihaM2G avatar Sep 20 '22 17:09 ThihaM2G

I exactly have the same problem with nearly the same setup. One nginx reverse proxy who serves keycloak and my other nginx who acts as a web server whose image is baased on openresty and have the lua code...

KevinKeo avatar Sep 21 '22 15:09 KevinKeo

I exactly have the same problem with nearly the same setup. One nginx reverse proxy who serves keycloak and my other nginx who acts as a web server whose image is baased on openresty and have the lua code...

Oh, did you manage to solve it? I'm about to give up on this problem. I wanted Nginx to handle this authentication process. Now, I'm looking for an alternative solution.

ThihaM2G avatar Sep 22 '22 04:09 ThihaM2G

No idea To me it looks like the nginx webserver who does that authentification, doesn't authenticate so it loops and redo the auth to keycloak (keycloak behind the nginx reverse proxy).

But I don't know that much about oidc and oauth so I am kind of lost.

KevinKeo avatar Sep 22 '22 12:09 KevinKeo

No idea To me it looks like the nginx webserver who does that authentification, doesn't authenticate so it loops and redo the auth to keycloak (keycloak behind the nginx reverse proxy).

But I don't know that much about oidc and oauth so I am kind of lost.

In my opinion, authentication works. That's why redirection occurs based on the redirect_uri_path parameter from the Lua script. redirect_uri_path is for redirection only after authentication succeeds. The problem seems related to our setup. If we have only one Nginx directly serving for both Lua script and Keycloak, we may not have that redirection loop. I don't have permission to modify the main Nginx. It's worth trying if you can. :)

ThihaM2G avatar Sep 22 '22 16:09 ThihaM2G

I have used a setup with two or even more proxies without problems in the past. It is possible. :-)

OpenID Connect uses a redirect based flow where - in your case - Keycloak redirects the user after successful login to a very specific URI in your application where code must be running that knows how to complete the OpenID Connect flow. redirect_uri is this very specific URI and it must be handled by lua-resty-openidc and can not be handeled by anything else. Once the flow has completed, lua-resty-openidc redirects to where the first request before the flow started wanted to go.

It looks as if your setup gets as far as the redirect from keycloak coming in to server2, so this part of the setu seems to be correct. Your redirect_uri should be something that does not correspond to any real URI you want to access, but to a URI that is only served by lua-resty-openidc. Something like /app/download/redirect_uri for example.

If you have set up things this way and it still fails, then you should have a look look at lua-resty-openidc's logs for hints (or post the logs here if you cannot see what's going wrong easily).

bodewig avatar Sep 25 '22 12:09 bodewig

I have used a setup with two or even more proxies without problems in the past. It is possible. :-)

Thanks for your confirmation. Appreciate it. :)

OpenID Connect uses a redirect based flow where - in your case - Keycloak redirects the user after successful login to a very specific URI in your application where code must be running that knows how to complete the OpenID Connect flow. redirect_uri is this very specific URI and it must be handled by lua-resty-openidc and can not be handeled by anything else. Once the flow has completed, lua-resty-openidc redirects to where the first request before the flow started wanted to go.

It looks as if your setup gets as far as the redirect from keycloak coming in to server2, so this part of the setu seems to be correct. Your redirect_uri should be something that does not correspond to any real URI you want to access, but to a URI that is only served by lua-resty-openidc. Something like /app/download/redirect_uri for example.

If you have set up things this way and it still fails, then you should have a look look at lua-resty-openidc's logs for hints (or post the logs here if you cannot see what's going wrong easily).

So you mean redirect_uri_path shouldn't represent any actual URI? In my Lua script, it is pointing to an actual URI (depending on the requested file). Otherwise, I got 404. Am I missing something? (I edited my Nginx config file to include the location block.) Thanks for your help.

ThihaM2G avatar Sep 25 '22 15:09 ThihaM2G

redirect_uri must point to an URI inside a location with a Lua access block that invokes lua-resty-openidc. So it would be a constant URI that is inside of /app/download/ - lua-resty-openidc itself provides the implementation of it, so it is a real URI :-)

bodewig avatar Sep 25 '22 18:09 bodewig

redirect_uri must point to an URI inside a location with a Lua access block that invokes lua-resty-openidc. So it would be a constant URI that is inside of /app/download/ - lua-resty-openidc itself provides the implementation of it, so it is a real URI :-)

I do redirect to an not existing link.

My keycloak is behind the path /auth My app is behind /app/name-app

And the redirect uri is /app/name-app/redirect-uri

Sadly, I still get an infinite loop.

Do you know how to show or read the openidc log ?

When I removed the lua code from the nginx webserver (app) and put it in the reverse proxy, it was working correclty. But having the lua code behind a reverse proxy is still not working.

KevinKeo avatar Sep 26 '22 08:09 KevinKeo

We also have a reverse proxy in front of our app, and the app has an nginx sidecar with lua-resty-openidc. The issue we have is that the redirect_uri needs to be:

https://host/app-name/callback 

The front reverse proxy uses the app-name to route, but the app is unaware of the "app-name" prefix, and only has URLs like /callback, /path1, /path2, etc.

What I want to do is tell lua-rest-openidc that the redirect_uri (that it passes to the authentication server) is https://host/app-name/callback, but that it should recognize the authorization callback when it gets a request on path /callback. It's like I want to provide it with:

opts.redirect_uri => The full redirection URI to give to the auth server opts.redirect_uri_path => The path to recognize as a authorization callback

It seems that if I put the "opts.redirect_uri_path" in FRONT of the "and" statement that this would work:

https://github.com/zmartzone/lua-resty-openidc/blob/master/lib/resty/openidc.lua#L1437

Is there another way to have an external redirect_uri, but to tell lua-resty-openidc to use a specific path as the redirection / callback path?

raythree avatar Sep 26 '22 22:09 raythree

@raythree I think you are correct and lua-resty-openidc needs a way to properly deal proxies that rewrite URIs - but from this issues's description I don't believe this is the case here. In either case this would be worth a separate issue.

If you happen to open a new one it would be good if you could check whether your reverse proxy sets X-Forwarded-Prefix which is yet another non-standard header that would contain /app-name in your case. This might help in the common case of prefix based routing but a more general solution (like allowing users to set both redirect_uri and a local path separately) is probably needed to deal with more complex rewrite rules.

bodewig avatar Sep 27 '22 05:09 bodewig

We also have a reverse proxy in front of our app, and the app has an nginx sidecar with lua-resty-openidc. The issue we have is that the redirect_uri needs to be:

https://host/app-name/callback 

The front reverse proxy uses the app-name to route, but the app is unaware of the "app-name" prefix, and only has URLs like /callback, /path1, /path2, etc.

What I want to do is tell lua-rest-openidc that the redirect_uri (that it passes to the authentication server) is https://host/app-name/callback, but that it should recognize the authorization callback when it gets a request on path /callback. It's like I want to provide it with:

opts.redirect_uri => The full redirection URI to give to the auth server opts.redirect_uri_path => The path to recognize as a authorization callback

It seems that if I put the "opts.redirect_uri_path" in FRONT of the "and" statement that this would work:

https://github.com/zmartzone/lua-resty-openidc/blob/master/lib/resty/openidc.lua#L1437

Is there another way to have an external redirect_uri, but to tell lua-resty-openidc to use a specific path as the redirection / callback path?

Indeed the problem seems to come from that. In my nginx web server, I put all the block in the same path as the reverse proxy use. And now I am using an alias to serve my static file under the request_uri path used in the reverse proxy and it seems to work.

KevinKeo avatar Sep 29 '22 16:09 KevinKeo

Indeed the problem seems to come from that.

So we should be able to cover that when #453 is addressed. I doubt it is the same case for @ThihaM2G, though.

bodewig avatar Sep 29 '22 16:09 bodewig

So we should be able to cover that when #453 is addressed. I doubt it is the same case for @ThihaM2G, though.

Yes, it's the redirection loop for my case. I couldn't be able to solve it, unfortunately. I had to implement the same logic in my Java backend project. Thanks for the help. :)

ThihaM2G avatar Oct 03 '22 04:10 ThihaM2G