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

Authorization bearer ignored if bearer_only=false #517

Closed james-mchugh closed 5 months ago

james-mchugh commented 5 months ago
Environment
Expected behaviour

If bearer_only=False and an access token is passed in the Authorization header, the access token will be used if the cookie is not present.

Actual behaviour

Access token in authorization header is ignored.

Minimized example

Apisix route config:

 routes:
    - uri: /frontend/*
       plugins:
          openid-connect:
            client_id: "${{OIDC_CLIENT_ID}}"
            client_secret: "${{OIDC_CLIENT_SECRET}}"
            discovery: "${{OIDC_DISCOVERY_URL}}"
            realm: "${{OIDC_REALM}}"
            bearer_only: false
            session:
              secret: "${{OIDC_SESSION_SECRET}}"
      service_id: 1
    - uri: /backend/*
       plugins:
          openid-connect:
            client_id: "${{OIDC_CLIENT_ID}}"
            client_secret: "${{OIDC_CLIENT_SECRET}}"
            discovery: "${{OIDC_DISCOVERY_URL}}"
            realm: "${{OIDC_REALM}}"
            unauth_action: deny
            bearer_only: false
            session:
              secret: "${{OIDC_SESSION_SECRET}}"
      service_id: 2
Configuration and NGINX server log files
2024/06/07 20:07:30 [warn] 15#15: *50093 [lua] plugin.lua:1160: run_plugin(): openid-connect exits with http status code 401, client: 10.42.0.205, server: _, request: "POST /backend/ HTTP/1.1", host: "ubuntu"
10.42.0.205 - - [07/Jun/2024:20:07:30 +0000] ubuntu "POST /backend/ HTTP/1.1" 401 251 0.000 "-" "curl/8.4.0" - - - "http://ubuntu"
Investigation

This appears to happen due to the openidc_get_bearer_access_token function checking accept_token_as to see where it should source the token from (cookie or authorization header). If this is set to cookie, it automatically short circuits and attempts to pull the token from the cookie at https://github.com/zmartzone/lua-resty-openidc/blob/master/lib/resty/openidc.lua#L1637. There does not seem to be any fallback handling if the handling of the cookie fails.

This may be intended behavior. The Apisix documentation seemed to indicate that the authorization header or cookie could be present if bearer_only is false, but it may be incorrect.

Use Case

For my use case, I have a browser-based application and a "legacy" CLI-based application. They will both be accessing the same backend server through a gateway (Apisix), where your plugin is being used to enforce authentication. For the browser-based authentication, when the frontend server is accessed, the request goes through the Apisix route using the lua-resty-openidc plugin and receives a cookie upon successful authentication. This works great. The browser is then expected to send requests directly to the backend and include the cookie so Apisix and lua-resty-openidc can validate the requests are authenticated.

For the CLI application, that will need to store the token locally and include that in the Authorization header to requests to the backend server. This is going through the same route as the browser's request to the backend server, as the hostname and URL path are the same. The expectation here was that the lua-resty-openidc plugin would still check the authorization header even if bearer_only=false, but that does not appear to be happening. Instead, I always get 401 responses from Apisix when trying to access these endpoints.

Are there other ways of handling this that make more sense?

james-mchugh commented 5 months ago

For anyone experiencing a similar issue, I was able to work around this in Apisix by utilizing the radixtree router. I created a variable-based router to utilize the openidc plugin with the relying party flow only if the request's user agent string includes tokens that typically appear in browsers (chrome, firefox, etc.). Otherwise, bearer only authentication is used. Below is an example configuration:

routes:
  - uri: /frontend/*
    plugins:
      openid-connect:
        client_id: "${{OIDC_CLIENT_ID}}"
        client_secret: "${{OIDC_CLIENT_SECRET}}"
        discovery: "${{OIDC_DISCOVERY_URL}}"
        realm: "${{OIDC_REALM}}"
        bearer_only: false
        session:
          secret: "${{OIDC_SESSION_SECRET}}"
    service_id: 1

  # For browsers, handle the session cookie at the route level.
  - uri: /backend/*
    vars:
      - - "http_user_agent"
        - "~*"
        - "(chromium|chrome|firefox|safari|opr|opera|seamonkey)"
    plugins:
      openid-connect:
        client_id: "${{OIDC_CLIENT_ID}}"
        client_secret: "${{OIDC_CLIENT_SECRET}}"
        discovery: "${{OIDC_DISCOVERY_URL}}"
        realm: "${{OIDC_REALM}}"
        unauth_action: deny
        bearer_only: false
        session:
          secret: "${{OIDC_SESSION_SECRET}}"
    service_id: 2

  # For other clients, defer authentication to the service 
  - uri: /backend/*
    service_id: 2
services:
  - id: 1
    upstream:
      nodes:
        "frontend:80": 1
  - id: 2
    upstream:
      nodes:
        "backend:80": 1 

    # Fall back to bearer only authentication to ensure all requests to the backend are authenticated
    plugins:
      openid-connect:
        client_id: "${{OIDC_CLIENT_ID}}"
        client_secret: "${{OIDC_CLIENT_SECRET}}"
        discovery: "${{OIDC_DISCOVERY_URL}}"
        realm: "${{OIDC_REALM}}"
        bearer_only: true

I do still think it's worth considering ways in lua-resty-openidc to make this more straightforward.

james-mchugh commented 5 months ago

After pouring over the lua-rest-openidc code and the Apisix plugin code for a little while, I realized this is probably more of a problem with the Apisix plugin code. Sorry for the confusion. I'll go ahead and close this