zmartzone / lua-resty-openidc

OpenID Connect Relying Party and OAuth 2.0 Resource Server implementation in Lua for NGINX / OpenResty
Apache License 2.0
976 stars 249 forks source link

How to Share Authentication Between Different Applications? #527

Closed zRich closed 1 month ago

zRich commented 1 month ago

Environment

Use Case

I have two separate applications running with OpenResty:

  1. First application: Developed by others, this app does not have its own authentication mechanism, but it needs to be protected.
  2. Second application: A Next.js app that uses keycloak.js for authentication with Keycloak 25.0.3.

The Next.js application successfully authenticates users via keycloak.js and retrieves access_token, refresh_token, and id_token from Keycloak.

Question

How can I authenticate users in the first application if they already have valid access_token and refresh_token obtained via the Next.js application?

My Current Approach

  1. Once keycloak.js retrieves the access_token and refresh_token, these tokens are sent to OpenResty.
  2. In OpenResty, I save the access_token, refresh_token, and set access_token_expiration to 0 (essentially faking the token expiration).
  3. I then call openidc.access_token to refresh the tokens using the stored refresh_token.

Here’s the relevant code:

local cjson = require "cjson.safe"

ngx.req.read_body()
local body = ngx.req.get_body_data()
local data, err = cjson.decode(body)
if err then
    ngx.status = ngx.HTTP_BAD_REQUEST  -- 400 Bad Request
    ngx.say("Invalid request body")
end
local r_session = require("resty.session")
local session = r_session.start()
session:set("access_token", data.access_token)
session:set("refresh_token", data.refresh_token)
session:set("access_token_expiration", 0)
session:save()

-- Attempt to refresh access token
local token, err = openidc.access_token(opts, session)

In openidc.access_token, I use the following logic:

function openidc.access_token(opts, session_opts)
  log(DEBUG, "session_opts: ", dump(session_opts.data))
  local session = r_session.start(session_opts)
  log(DEBUG, "session : ", dump(session.data))
  local token, err = openidc_access_token(opts, session, true)
  session:close()
  return token, err
end

Problem

Before calling local session = r_session.start(session_opts), the session_opts object contains all the expected values (access_token, refresh_token, and access_token_expiration). However, after this line, these values are missing in the session.

I'm not sure why the session data gets lost after calling r_session.start(session_opts). Is there something wrong with the session handling, or am I missing a step in sharing the tokens across applications?

Any help or guidance on how to properly manage authentication sharing between applications would be greatly appreciated!

oldium commented 1 month ago

Why it does not work: You have a call like r_session.start(r_session.start()). The lua-resty-session is not designed to handle calls like that. openidc.access_token() accepts session options to start the session, not a session instance.

For the described scenario you just need to check whether the access_token is valid, either with openidc.bearer_jwt_verify() or directly with lua-resty-jwt library.

zRich commented 1 month ago

Why it does not work: You have a call like r_session.start(r_session.start()). The lua-resty-session is not designed to handle calls like that. openidc.access_token() accepts session options to start the session, not a session instance.

For the described scenario you just need to check whether the access_token is valid, either with openidc.bearer_jwt_verify() or directly with lua-resty-jwt library.

Thanks for your reply @oldium

I use session to save other data, so I pass a session instance.

I changed my solution temporary. I authenticate user through username/password with keycloak rest api to get access_token, then use lua-resty-openidc to verify access_token.