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

How to force users to login again after their session was closed in keycloak?

Open andiarbeit12 opened this issue 3 years ago • 6 comments

Hi!

I´m currently trying to set up Openresty as reverse proxy with Keycloak as IdP in Kubernetes. Accessing the proxied service works as expected and I also can logout from the proxied service via /logout.

However, when I terminate the user's session in Keycloak, the user can still refresh and use the site. I want the user to have to log in again when the session is closed. How can i achieve this?

Environment
  • openresty-version: openresty/1.21.4.1
  • lua-resty-openidc version: 1.7.6-3
  • OpenID Connect provider: Keycloak: 19.0.1 - Community
Expected behaviour

Users have to log in again, when their session is closed in Keycloak.

Actual behaviour

When i sign out all active sessions in Keycloak the user can still use and refresh the proxied site without logging in again.

Configuration and NGINX server log files

default.conf:

upstream backend {
    server nginx-test:80;
}

 
server {
      listen   8080;
      root     /opt/nginx/html;

      resolver kube-dns.kube-system.svc.cluster.local;
      # disabled caching so the browser won't cache the site
      expires           0;
      add_header        Cache-Control private;
      
      lua_ssl_verify_depth 2;
      lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.pem;

      # log paths
      error_log /var/log/nginx/error.log;
      access_log /var/log/nginx/access.log;

      access_by_lua_block {
      local opts = {
          redirect_uri = "/redirect_uri",
          discovery = "<my-keycloak-url>/realms/<realm-name>/.well-known/openid-configuration",
          client_id = "<client-id>",
          client_secret = "<client_secret>",
          scope = "openid",
          redirect_uri_scheme = "https",
          logout_path = "/logout",
          redirect_after_logout_uri = "<my-keycloak-url>/realms/<realm-name>/protocol/openid-connect/logout",
          redirect_after_logout_with_id_token_hint = false,
          ssl_verify = true,
          session_contents = { id_token = true},
      }

      local res, err = require("resty.openidc").authenticate(opts)

      if err then
          ngx.status = 403
          ngx.say(err)
          ngx.exit(ngx.HTTP_FORBIDDEN)
      end

      ngx.req.set_header("X-USER", res.id_token.sub)
    }
 
location / {

    proxy_pass http://backend;
    # set headers
    # https://www.keycloak.org/server/reverseproxy
    proxy_set_header    X-Forwarded-For         $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Port $server_port;
  }
}

Client Settings in Keycloak:

Client authentication: On
Authorization: Off
Authentication flow:
- Standard flow: On
- Direct access grants: On
- Implicit flow: Off
- Service accounts roles: Off
- OAuth 2.0 Device Authorization Grant: Off
- OIDC CIBA Grant: Off 

I appreciate any help!

andiarbeit12 avatar Apr 18 '23 12:04 andiarbeit12

authenticate doesn't normally check whether a token is revoked before it expires as this would have a major performance impact. The usual approach is to make token lifetimes really short so the window of time between token revocation and expiration becomes small. If you can not limit token life time to an acceptable value opts.refresh_session_interval may be another option which would check whether the login is still valid on a regular basis.

bodewig avatar Apr 18 '23 20:04 bodewig

Thanks for your answer!

I'm not sure I completely understood the first suggested solution. In Keycloak there is only an option to limit the lifespan for the access token and in the nginx.conf there are only options for the access token aswell. Am I missing something or do I need an access_token in my session_contents to make use of the token lifetimes?

I have added the refresh_session_interval option to the local options. The user now has to log in again when opening a new tab with the protected web page after the session was terminated in Keycloak. However, the user can still use the protected page in an active tab.

andiarbeit12 avatar Apr 24 '23 11:04 andiarbeit12

What I meant with the first option was something like "limit the access token lifespan to two minutes and just accept a token could be used for the remaining max two minutes of its lifespan even after it has been revoked". This is often an acceptable compromise.

For the second option: when refresh_session_interval elapses lua-resty-openidc will trigger a so called "silent" authentication in which the client is redirected to the authorization endpoint with parameter prompt=none which is supposed to return a new authorization code if the OIDC provider still thinks the session is valid and an error otherwise. This implies the OIDC provider knows about the user's login state. Please don't assume anybody here would know anything specific to any OIDC provider.

If you use two different tabs that share the same lua-resty-openidc session cookie than I can not explain why a fresh tab should behave any different from an already existing one. What you may be seeing could be a timing issue. lua-resty-openidc will only trigger the silent authentication redirect once the configured interval has passed since the user has last been logged in. If you log out the user this period may not have expired, yet.

bodewig avatar Apr 24 '23 14:04 bodewig

Thanks again now I get it! I have set the refresh_session_interval = 60 and it seems like i was mistaken the user can still use the page.

nginx/error.log

2023/04/25 14:53:24 [error] 6#6: *299 [lua] openidc.lua:1098: authenticate(): unhandled request to the redirect_uri: /redirect_uri?error=login_required&state=c1f159c1e579b0bec23e8f2be60a91a8, client: 10.1.11.0, server: , request: "GET /redirect_uri?error=login_required&state=c1f159c1e579b0bec23e8f2be60a91a8 HTTP/1.1", host: "<public-url-of-my-proxied-service>"
2023/04/25 14:53:49 [error] 6#6: *547 [lua] openidc.lua:1098: authenticate(): unhandled request to the redirect_uri: /redirect_uri?error=login_required&state=7f5878339a2891c83fbe3f85e05a7dbe, client: 10.1.11.0, server: , request: "GET /redirect_uri?error=login_required&state=7f5878339a2891c83fbe3f85e05a7dbe HTTP/1.1", host: "<public-url-of-my-proxied-service>"

keycloak.log

2023-04-25 14:51:25,267 WARN  [org.keycloak.services.managers.AuthenticationManager] (executor-thread-558) Some clients have been not been logged out for user <user> in testnginx realm: nginx
2023-04-25T14:51:45.644128121Z 2023-04-25 14:51:45,642 WARN  [org.keycloak.protocol.oidc.utils.AcrUtils] (executor-thread-558) Invalid realm configuration (ACR-LOA map)
2023-04-25T14:54:45.757080442Z 2023-04-25 14:54:45,756 WARN  [org.keycloak.events] (executor-thread-558) type=LOGIN_ERROR, realmId=be5515cd-0c52-473c-b0b8-3ca9905a0413, clientId=null, userId=null, ipAddress=10.18.1.208, error=invalid_request
2023-04-25 14:54:59,818 WARN  [org.keycloak.protocol.oidc.utils.AcrUtils] (executor-thread-558) Invalid realm configuration (ACR-LOA map)
2023-04-25T14:55:40.179911220Z 2023-04-25 14:55:40,179 WARN  [org.keycloak.protocol.oidc.utils.AcrUtils] (executor-thread-558) Invalid realm configuration (ACR-LOA map)

Seems like there is a issue with in keycloak configuration. I will investigate this further. Appreciate any suggestions!

andiarbeit12 avatar Apr 25 '23 15:04 andiarbeit12

The nginx logs look as if silent re-authentication failed - keycloak redirects back with a "login_required" error. This looks fine.

For this calls openidc.authenticate should return a non-nil errand your code should enter the branch that ends up sending a 403 status back to the client.

bodewig avatar Apr 25 '23 16:04 bodewig