Giter VIP home page Giter VIP logo

lua-resty-openidc's Introduction

CI Status OpenID Certification

lua-resty-openidc

lua-resty-openidc is a library for NGINX implementing the OpenID Connect Relying Party (RP) and/or the OAuth 2.0 Resource Server (RS) functionality.

When used as an OpenID Connect Relying Party it authenticates users against an OpenID Connect Provider using OpenID Connect Discovery and the Basic Client Profile (i.e. the Authorization Code flow). When used as an OAuth 2.0 Resource Server it can validate OAuth 2.0 Bearer Access Tokens against an Authorization Server or, in case a JSON Web Token is used for an Access Token, verification can happen against a pre-configured secret/key .

It maintains sessions for authenticated users by leveraging lua-resty-session thus offering a configurable choice between storing the session state in a client-side browser cookie or use in of the server-side storage mechanisms shared-memory|memcache|redis.

It supports server-wide caching of resolved Discovery documents and validated Access Tokens.

It can be used as a reverse proxy terminating OAuth/OpenID Connect in front of an origin server so that the origin server/services can be protected with the relevant standards without implementing those on the server itself.

Dependencies

lua-resty-openidc depends on the following packages:

The dependencies above come automatically with OpenResty. You will need to install two extra pure-Lua dependencies that implement session management and HTTP client functions:

Typically - when running as an OpenID Connect RP or an OAuth 2.0 server that consumes JWT access tokens - you'll also need to install the following dependency:

The lua-resty-jwt dependency above is not required when running as an OAuth 2.0 Resource Server (only) using remote introspection for access token validation.

Installation

Using luarocks execute the following:

 luarocks install lua-resty-openidc

Otherwise copy openidc.lua somewhere in your lua_package_path under a directory named resty. If you are using OpenResty, the default location would be /usr/local/openresty/lualib/resty.

Older versions of lua-resty-openidc could also be installed using opm but this is no longer supported.

Sample Configuration for Google+ Signin

Sample nginx.conf configuration for authenticating users against Google+ Signin, protecting a reverse-proxied path.

events {
  worker_connections 128;
}

http {

  lua_package_path '~/lua/?.lua;;';

  resolver 8.8.8.8;

  lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
  lua_ssl_verify_depth 5;

  # cache for discovery metadata documents
  lua_shared_dict discovery 1m;
  # cache for JWKs
  lua_shared_dict jwks 1m;

  # NB: if you have "lua_code_cache off;", use:
  # set $session_secret xxxxxxxxxxxxxxxxxxx;
  # see: https://github.com/bungle/lua-resty-session#notes-about-turning-lua-code-cache-off

  server {
    listen 8080;

    location / {

      access_by_lua_block {

          local opts = {
             -- the full redirect URI must be protected by this script
             -- if the URI starts with a / the full redirect URI becomes
             -- ngx.var.scheme.."://"..ngx.var.http_host..opts.redirect_uri
             -- unless the scheme was overridden using opts.redirect_uri_scheme or an X-Forwarded-Proto header in the incoming request
             redirect_uri = "https://MY_HOST_NAME/redirect_uri",
             -- up until version 1.6.1 you'd specify
             -- redirect_uri_path = "/redirect_uri",
             -- and could not set the hostname

             -- The discovery endpoint of the OP. Enable to get the URI of all endpoints (Token, introspection, logout...)
             discovery = "https://accounts.google.com/.well-known/openid-configuration",

             -- Access to OP Token endpoint requires an authentication. Several authentication modes are supported:
             --token_endpoint_auth_method = ["client_secret_basic"|"client_secret_post"|"private_key_jwt"|"client_secret_jwt"],
             -- o If token_endpoint_auth_method is set to "client_secret_basic", "client_secret_post", or "client_secret_jwt", authentication to Token endpoint is using client_id and client_secret
             --   For non compliant OPs to OAuth 2.0 RFC 6749 for client Authentication (cf. https://tools.ietf.org/html/rfc6749#section-2.3.1)
             --   client_id and client_secret MUST be invariant when url encoded
             client_id = "<client_id>",
             client_secret = "<client_secret>",
             -- o If token_endpoint_auth_method is set to "private_key_jwt" authentication to Token endpoint is using client_id, client_rsa_private_key and client_rsa_private_key_id to compute a signed JWT
             --   client_rsa_private_key is the RSA private key to be used to sign the JWT generated by lua-resty-openidc for authentication to the OP
             --   client_rsa_private_key_id (optional) is the key id to be set in the JWT header to identify which public key the OP shall use to verify the JWT signature
             --client_id = "<client_id>",
             --client_rsa_private_key=[[-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAiThmpvXBYdur716D2q7fYKirKxzZIU5QrkBGDvUOwg5izcTv
[...]
h2JHukolz9xf6qN61QMLSd83+kwoBr2drp6xg3eGDLIkQCQLrkY=
-----END RSA PRIVATE KEY-----]],
             --client_rsa_private_key_id="key id#1",
             --   Life duration expressed in seconds of the signed JWT generated by lua-resty-openidc for authentication to the OP.
             --   (used when token_endpoint_auth_method is set to "private_key_jwt" or "client_secret_jwt" authentication). Default is 60 seconds.
             --client_jwt_assertion_expires_in = 60,
             -- When using https to any OP endpoints, enforcement of SSL certificate check can be mandated ("yes") or not ("no").
             --ssl_verify = "no",
             -- Connection keepalive with the OP can be enabled ("yes") or disabled ("no").
             --keepalive = "no",

             --response_mode=form_post can be used to make lua-resty-openidc use the [OAuth 2.0 Form Post Response Mode](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html). *Note* for modern browsers you will need to set [`$session_cookie_samesite`](https://github.com/bungle/lua-resty-session#string-sessioncookiesamesite) to `None` with form_post unless your OpenID Connect Provider and Relying Party share the same domain.
             --authorization_params = { hd="zmartzone.eu" },
             --scope = "openid email profile",
             -- Refresh the users id_token after 900 seconds without requiring re-authentication
             --refresh_session_interval = 900,
             --iat_slack = 600,
             --redirect_uri_scheme = "https",
             --logout_path = "/logout",
             --redirect_after_logout_uri = "/",
             -- Where should the user be redirected after logout from the RP. This option overides any end_session_endpoint that the OP may have provided in the discovery response.
             --redirect_after_logout_with_id_token_hint = true,
             -- Whether the redirection after logout should include the id token as an hint (if available). This option is used only if redirect_after_logout_uri is set.
             --post_logout_redirect_uri = "https://www.zmartzone.eu/logoutSuccessful",
             -- Where does the RP requests that the OP redirects the user after logout. If this option is set to a relative URI, it will be relative to the OP's logout endpoint, not the RP's.

             --accept_none_alg = false
             -- if your OpenID Connect Provider doesn't sign its id tokens
             -- (uses the "none" signature algorithm) then set this to true.

             --accept_unsupported_alg = true
             -- if you want to reject tokens signed using an algorithm
             -- not supported by lua-resty-jwt set this to false. If
             -- you leave it unset or set it to true, the token signature will not be
             -- verified when an unsupported algorithm is used.

             --renew_access_token_on_expiry = true
             -- whether this plugin shall try to silently renew the access token once it is expired if a refresh token is available.
             -- if it fails to renew the token, the user will be redirected to the authorization endpoint.
             --access_token_expires_in = 3600
             -- Default lifetime in seconds of the access_token if no expires_in attribute is present in the token endpoint response.

             --access_token_expires_leeway = 0
             --  Expiration leeway for access_token renewal. If this is set, renewal will happen access_token_expires_leeway seconds before the token expiration. This avoids errors in case the access_token just expires when arriving to the OAuth Resource Server.

             --force_reauthorize = false
             -- When force_reauthorize is set to true the authorization flow will be executed even if a token has been cached already
             --session_contents = {id_token=true}
             -- Whitelist of session content to enable. This can be used to reduce the session size.
             -- When not set everything will be included in the session.
             -- Available are:
             -- id_token, enc_id_token, user, access_token (includes refresh token)

             -- You can specify timeouts for connect/send/read as a single number (setting all timeouts) or as a table. Values are in milliseconds
             -- timeout = 1000
             -- timeout = { connect = 500, send = 1000, read = 1000 }

             --use_nonce = false
             -- By default the authorization request includes the
             -- nonce paramter. You can use this option to disable it
             -- which may be necessary when talking to a broken OpenID
             -- Connect provider that ignores the paramter as the
             -- id_token will be rejected otherwise.

             --revoke_tokens_on_logout = false
             -- When revoke_tokens_on_logout is set to true a logout notifies the authorization server that previously obtained refresh and access tokens are no longer needed. This requires that revocation_endpoint is discoverable.
             -- If there is no revocation endpoint supplied or if there are errors on revocation the user will not be notified and the logout process continues normally.

             -- Optional : use outgoing proxy to the OpenID Connect provider endpoints with the proxy_opts table :
             -- this requires lua-resty-http >= 0.12
             -- proxy_opts = {
             --    http_proxy  = "http://<proxy_host>:<proxy_port>/",
             --    https_proxy = "http://<proxy_host>:<proxy_port>/"
             -- }

             -- Lifecycle Hooks
             --
             -- lifecycle = {
             --    on_created = handle_created,
             --    on_authenticated = handle_authenticated,
             --    on_regenerated = handle_regenerated
             --    on_logout = handle_logout
             -- }
             --
             -- where `handle_created`, `handle_authenticated`, `handle_regenerated` and `handle_logout` are callables
             -- accepting a single argument `session`
             --
             --  -- `on_created` hook is invoked *after* a session has been created in
             --     `openidc_authorize` immediately prior to saving the session
             --  -- `on_authenticated` hook is invoked *after* receiving authorization response in
             --     `openidc_authorization_response` immediately prior to saving the session
             --     Starting with lua-resty-openidc 1.7.5 this receives the decoded id_token as second and the response of the token endpoint as third argument      
             --  -- `on_regenerated` is invoked immediately after the
                     a new access token has been obtained via token
                     refresh and is called with the regenerated session table
             --  -- `on_logout` hook is invoked *before* a session is destroyed in
             --     `openidc_logout`
             --
             --  Any, all or none of the hooks may be used. Empty `lifecycle` does nothing.
             --  A hook that returns a truthy value causes the lifecycle action they are taking part of to fail.

             -- Optional : add decorator for HTTP request that is
             -- applied when lua-resty-openidc talks to the OpenID Connect
             -- provider directly. Can be used to provide extra HTTP headers
             -- or add other similar behavior.
             -- http_request_decorator = function(req)
             --   local h = req.headers or {}
             --   h[EXTRA_HEADER] = 'my extra header'
             --   req.headers = h
             --   return req
             -- end,

             -- use_pkce = false,
             -- when set to true the "Proof Key for Code Exchange" as
             -- defined in RFC 7636 will be used. The code challenge
             -- method will alwas be S256

          }

          -- call authenticate for OpenID Connect user authentication
          local res, err = require("resty.openidc").authenticate(opts)

          if err then
            ngx.status = 500
            ngx.say(err)
            ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
          end

          -- at this point res is a Lua table with 3 keys:
          --   id_token    : a Lua table with the claims from the id_token (required)
          --   access_token: the access token (optional)
          --   user        : a Lua table with the claims returned from the user info endpoint (optional)

          --if res.id_token.hd ~= "zmartzone.eu" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end

          --if res.user.email ~= "[email protected]" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end

          -- set headers with user info: this will overwrite any existing headers
          -- but also scrub(!) them in case no value is provided in the token
          ngx.req.set_header("X-USER", res.id_token.sub)
      }

      proxy_pass http://localhost:80;
    }
  }
}

About redirect_uri

The so called redirect_uri is an URI that is part of the OpenID Connect protocoll. The redirect URI is registered with your OpenID Connect provider and is the URI your provider will redirect the users to after successful login. This URI then is handelled by lua-resty-openidc where it obtains tokens and performs some checks and only after that the browser is redirected to where your user wanted to go initially.

The redirect_uri is not expected to be handelled by your appication code at all. It must be an URI wthat lua-resty-openidc is responsible for so it must be in a location protected by lua-resty-openidc.

You configure the redirect_uri on the lua-resty-openidc side via the opts.redirect_uri parameter (which defaults to /redirect_uri). If it starts with a / then lua-resty-openidc will prepend the protocoll and current hostname to it when sending the URI to the OpenID Connect provider (taking Forwarded and X-Forwarded-* HTTP headers into account). But you can also specify an absolute URI containing host and protocoll yourself.

Before version 1.6.1 opts.redirect_uri_path has been the way to configure the redirect_uri without any option to take control over the protocoll and host parts.

Whenever lua-resty-openidc "sees" a local path navigated that matches the path of opts.redirect_uri (or opts.redirect_uri_path) it will intercept the request and handle it itself.

This works for most cases but sometimes the externally visible redirect_uri has a different path than the one locally visible to the server. This may happen if a reverse proxy in front of your server rewrites URIs before forwarding the requests. Therefore version 1.7.6 introduced a new option opts.local_redirect_uri_path. If it is set lua-resty-opendic will intercepts requests to this path rather than the path of opts.redirect_uri.

Check authentication only

-- check session, but do not redirect to auth if not already logged in
local res, err = require("resty.openidc").authenticate(opts, nil, "pass")

Check authentication only and deny unauthenticated access

-- check session, do not redirect to auth if not already logged in but return an error instead
local res, err = require("resty.openidc").authenticate(opts, nil, "deny")

Sessions and Locking

The authenticate function returns the current session object as its forth return argument. If you have configured lua-resty-session to use a server side storage backend that uses locking, the session may still be locked when it is returned. In this case you may want to close it explicitly

local res, err, target, session = require("resty.openidc").authenticate(opts)
session:close()

Caching

lua-resty-openidc can use shared memory caches for several things. If you want it to use the caches, you must use lua_shared_dict in your nginx.conf file.

Currently up to four caches are used

  • the cache named discovery stores the OpenID Connect Disovery metadata of your OpenID Connect Provider. Cache items expire after 24 hours unless overriden by opts.discovery_expires_in (a value given in seconds) . This cache will store one item per issuer URI and you can look up the discovery document yourself to get an estimate for the size required - usually a few kB per OpenID Connect Provider.
  • the cache named jwks stores the key material of your OpenID Connect Provider if it is provided via the JWKS endpoint. Cache items expire after 24 hours unless overriden by opts.jwks_expires_in. This cache will store one item per JWKS URI and you can look up the jwks yourself to get an estimate for the size required - usually a few kB per OpenID Connect Provider.
  • the cache named introspection stores the result of OAuth2 token introspection. Cache items expire when the corresponding token expires. Tokens with unknown expiry are not cached at all. This cache will contain one entry per introspected access token - usually this will be a few kB per token.
  • the cache named jwt_verification stores the result of JWT verification. Cache items expire when the corresponding token expires. Tokens with unknown expiry are not cached for two minutes. This cache will contain one entry per verified JWT - usually this will be a few kB per token.

Caching of Introspection and JWT Verification Results

Note the jwt_verification and introspection caches are shared between all configured locations. If you are using locations with different opts configuration the shared cache may allow a token that is valid for only one location to be accepted by another if it is read from the cache. In order to avoid cache confusion it is recommended to set opts.cache_segment to unique strings for each set of related locations.

Revoke tokens

The revoke_tokens(opts, session) function revokes the current refresh and access token. In contrast to a full logout, the session cookie will not be destroyed and the endsession endpoint will not be called. The function returns true if both tokens were revoked successfully. This function might be helpful in scenarios where you want to destroy/remove a session from the server side.

With revoke_token(opts, token_type_hint, token) it is also possible to revoke a specific token. token_type_hint can usually be refresh_token or access_token.

Sample Configuration for OAuth 2.0 JWT Token Validation

Sample nginx.conf configuration for verifying Bearer JWT Access Tokens against a pre-configured secret/key. Once successfully verified, the NGINX server may function as a reverse proxy to an internal origin server.

events {
  worker_connections 128;
}

http {

  lua_package_path '~/lua/?.lua;;';

  resolver 8.8.8.8;

  # cache for JWT verification results
  lua_shared_dict jwt_verification 10m;

  server {
    listen 8080;

    location /api {

      access_by_lua '

          local opts = {

            -- 1. example of a shared secret for HS??? signature verification
            --symmetric_key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
            -- in versions up to 1.6.1 this option's key would have been secret
            -- rather than symmetric_key

            -- 2. another example of a public cert for RS??? signature verification
            public_key = [[-----BEGIN CERTIFICATE-----
MIIC0DCCAbigAwIBAgIGAVSbMZs1MA0GCSqGSIb3DQEBCwUAMCkxCzAJBgNVBAYTAlVTMQwwCgYD
VQQKEwNibGExDDAKBgNVBAMTA2JsYTAeFw0xNjA1MTAxNTAzMjBaFw0yNjA1MDgxNTAzMjBaMCkx
CzAJBgNVBAYTAlVTMQwwCgYDVQQKEwNibGExDDAKBgNVBAMTA2JsYTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAIcLtHjX2GFxYv1033dvfohyCU6nsuR1qoDXfHTG3Mf/Yj4BfLHtMjJr
nR3sgHItH3B6qZPnfErfsN0LP4uZ10/74CrWVqT5dy6ecXMqYtz/KNJ8rG0vY8vltc417AU4fie8
gyeWv/Z6wHWUCf3NHRV8GfFgfuvywgUpHo8ujpUPFr+zrPr8butrzJPq1h3+r0f5P45tfWOdpjCT
gsTzK6urUG0k3WkwdDYapL3wRCAw597nYfgKzzXuh9N0ZL3Uj+eJ6BgCzUZDLXABpMBZfk6hmmzp
cAFV4nTf1AaAs/EOwVE0YgZBJiBrueMcteAIxKrKjEHgThU2Zs9gN9cSFicCAwEAATANBgkqhkiG
9w0BAQsFAAOCAQEAQLU1A58TrSwrEccCIy0wxiGdCwQbaNMohzirc41zRMCXleJXbtsn1vv85J6A
RmejeH5f/JbDqRRRArGMdLooGbqjWG/lwZT456Q6DXqF2plkBvh37kp/GjthGyR8ODJn5ekZwxuB
OcTuruRhqYOIJjiYZSgK/P0zUw1cjLwUJ9ig/O6ozYmof83974fygA/wK3SgFNEoFlTkTpOvZhVW
9kLfCVA/CRBfJNKnz5PWBBxd/3XSEuP/fcWqKGTy7zZso4MTB0NKgWO4duGTgMyZbM4onJPyA0CY
lAc5Csj0o5Q+oEhPUAVBIF07m4rd0OvAVPOCQ2NJhQSL1oWASbf+fg==
-----END CERTIFICATE-----]],
            -- in versions up to 1.6.1 this option's key would have been secret
            -- rather than public_key

            -- 3. alternatively one can point to a so-called Discovery document that
            -- contains "jwks_uri" entry; the jwks endpoint must provide either an "x5c" entry
            -- or both the "n" modulus and "e" exponent entries for RSA signature verification
            -- discovery = "https://accounts.google.com/.well-known/openid-configuration",

             -- the signature algorithm that you expect has been used;
             -- can be a single string or a table.
             -- You should set this for security reasons in order to
             -- avoid accepting a token claiming to be signed by HMAC
             -- using a public RSA key.
             --token_signing_alg_values_expected = { "RS256" }

             -- if you want to accept unsigned tokens (using the
             -- "none" signature algorithm) then set this to true.
             --accept_none_alg = false

             -- if you want to reject tokens signed using an algorithm
             -- not supported by lua-resty-jwt set this to false. If
             -- you leave it unset, the token signature will not be
             -- verified at all.
             --accept_unsupported_alg = true

             -- the expiration time in seconds for jwk cache, default is 1 day.
             --jwk_expires_in = 24 * 60 * 60

             -- It may be necessary to force verification for a bearer token and ignore the existing cached
             -- verification results. If so you need to set set the jwt_verification_cache_ignore option to true.
             -- jwt_verification_cache_ignore = true

             -- optional name of a cache-segment if you need separate
             -- caches for differently configured locations
             -- cache_segment = 'api'
          }

          -- call bearer_jwt_verify for OAuth 2.0 JWT validation
          local res, err = require("resty.openidc").bearer_jwt_verify(opts)

           if err or not res then
            ngx.status = 403
            ngx.say(err and err or "no access_token provided")
            ngx.exit(ngx.HTTP_FORBIDDEN)
          end

          -- at this point res is a Lua table that represents the (validated) JSON
          -- payload in the JWT token; now we typically do not want to allow just any
          -- token that was issued by the Authorization Server but we want to apply
          -- some access restrictions via client IDs or scopes

          --if res.scope ~= "edit" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end

          --if res.client_id ~= "ro_client" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end
      ';

       proxy_pass http://localhost:80;
    }
  }
}

Sample Configuration for PingFederate OAuth 2.0

Sample nginx.conf configuration for validating Bearer Access Tokens against a PingFederate OAuth 2.0 Authorization Server.

events {
  worker_connections 128;
}

http {

  lua_package_path '~/lua/?.lua;;';

  resolver 8.8.8.8;

  lua_ssl_trusted_certificate /opt/local/etc/openssl/cert.pem;
  lua_ssl_verify_depth 5;

  # cache for validation results
  lua_shared_dict introspection 10m;

  server {
    listen 8080;

    location /api {

      access_by_lua '

          local opts = {
             introspection_endpoint="https://localhost:9031/as/introspect.oauth2",
             client_id="rs_client",
             client_secret="2Federate",
             ssl_verify = "no",

             -- Defaults to "exp" - Controls the TTL of the introspection cache
             -- https://tools.ietf.org/html/rfc7662#section-2.2
             -- introspection_expiry_claim = "exp"

             -- optional name of a cache-segment if you need separate
             -- caches for differently configured locations
             -- cache_segment = 'api'
          }

          -- call introspect for OAuth 2.0 Bearer Access Token validation
          local res, err = require("resty.openidc").introspect(opts)

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

          -- at this point res is a Lua table that represents the JSON
          -- object returned from the introspection/validation endpoint

          --if res.scope ~= "edit" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end

          --if res.client_id ~= "ro_client" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end
      ';
    }
  }
}

Sample Configuration for passing bearer OAuth 2.0 access tokens as cookie

Sample nginx.conf configuration for validating Bearer Access Tokens passed as cookie against a ORY/Hydra Authorization Server.

events {
  worker_connections 128;
}

http {

  lua_package_path '~/lua/?.lua;;';

  resolver 8.8.8.8;

  lua_ssl_trusted_certificate /opt/local/etc/openssl/cert.pem;
  lua_ssl_verify_depth 5;

  # cache for validation results
  lua_shared_dict introspection 10m;

  server {
    listen 8080;

    location /api {

      access_by_lua '

          local opts = {
             -- sets the URI of the introspection endpoint
             introspection_endpoint="https://localhost:9031/oauth2/introspect",

             -- alternatively if your OAuth2 Provider provides a discovery document that contains the
             -- introspection_endpoint claim you can leave the introspection_endpoint option
             -- unset and instead use
             -- discovery = "https://my-oauth2-provider/.well-known/oauth-authorization-server",

             client_id="admin",
             client_secret="demo-password",
             ssl_verify = "no",

             -- Defines the interval in seconds after which a cached and introspected access token needs
             -- to be refreshed by introspecting (and validating) it again against the Authorization Server.
             -- When not defined the value is 0, which means it only expires after the `exp` (or alternative,
             -- see introspection_expiry_claim) hint as returned by the Authorization Server
             -- introspection_interval = 60,

             -- Defines the way in which bearer OAuth 2.0 access tokens can be passed to this Resource Server.
             -- "cookie" as a cookie header called "PA.global" or using the name specified after ":"
             -- "header" "Authorization: bearer" header
             -- When not defined the default "Authorization: bearer" header is used
             -- auth_accept_token_as = "cookie:PA",

             -- If header is used header field is Authorization
             -- auth_accept_token_as_header_name = "cf-Access-Jwt-Assertion"

             -- Authentication method for the OAuth 2.0 Authorization Server introspection endpoint,
             -- Used to authenticate the client to the introspection endpoint with a client_id/client_secret
             -- Defaults to "client_secret_post"
             -- introspection_endpoint_auth_method = "client_secret_basic",

             -- Specify the names of cookies separated by whitespace to pickup from the browser and send along on backchannel
             -- calls to the OP and AS endpoints.
             -- When not defined, no such cookies are sent.
             -- pass_cookies = "JSESSION"

             -- Defaults to "exp" - Controls the TTL of the introspection cache
             -- https://tools.ietf.org/html/rfc7662#section-2.2
             -- introspection_expiry_claim = "exp"

             -- It may be necessary to force an introspection call for an access_token and ignore the existing cached
             -- introspection results. If so you need to set set the introspection_cache_ignore option to true.
             -- introspection_cache_ignore = true

             -- optional name of a cache-segment if you need separate
             -- caches for differently configured locations
             -- cache_segment = 'api'
          }

          -- call introspect for OAuth 2.0 Bearer Access Token validation
          local res, err = require("resty.openidc").introspect(opts)

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

          -- at this point res is a Lua table that represents the JSON
          -- object returned from the introspection/validation endpoint

          --if res.scope ~= "edit" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end

          --if res.client_id ~= "ro_client" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end
      ';
    }
  }
}

Logging

Logging can be customized, including using custom logger and remapping OpenIDC's default log levels, e.g:

local openidc = require("resty.openidc")
openidc.set_logging(nil, { DEBUG = ngx.INFO })

Running Tests

We've created a dockerized setup for the test in order to simplify the installation of dependencies.

In order to run the tests perform

$ docker build -f tests/Dockerfile . -t lua-resty-openidc/test
$ docker run -it --rm lua-resty-openidc/test:latest

if you want to create luacov coverage while testing use

$ docker run -it --rm -e coverage=t lua-resty-openidc/test:latest

as the second command

Support

For generic questions, see the Wiki pages with Frequently Asked Questions at:
https://github.com/zmartzone/lua-resty-openidc/wiki
Any questions/issues should go to the Github Discussons or Issues tracker.

Disclaimer

This software is open sourced by ZmartZone IAM but not supported commercially as such. Any questions/issues should go to the Github Discussions or Issues tracker. See also the DISCLAIMER file in this directory.

lua-resty-openidc's People

Contributors

arcivanov avatar asbjornu avatar barrelmaker97 avatar bodewig avatar bungle avatar cdbattags avatar chipitsine avatar davidbirdsong avatar derekamz avatar dewiedn avatar dholth avatar dudssource avatar gdestuynder avatar gene1wood avatar gonzalad avatar grrolland avatar hanikesn avatar iperdomo avatar joshbarr avatar kg0r0 avatar membphis avatar oleeander avatar pamiel avatar petercoppensdatylon avatar therealcmj avatar thomasleplus avatar thorstenfleischmann avatar usysrc avatar zandbelt avatar zhuizhuhaomeng avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lua-resty-openidc's Issues

Add validation of hd param in id_token

Step 5 of validating and ID token listed over here: https://developers.google.com/identity/protocols/OpenIDConnect#validatinganidtoken

says:

If you passed a hd parameter in the request, verify that the ID token has a hd claim that matches your Google Apps hosted domain.

It seems like this functionality is missing from openidc_validate_id_token here:

https://github.com/pingidentity/lua-resty-openidc/blob/master/lib/resty/openidc.lua#L87

Without this is seems like it's not possible to restrict login access to a particular google apps domain.

outgoing proxy support

Hi, we've got lua-resty-openidc working, and are now trying to get it to work in an environment that requires all traffic to go through an outbound proxy. We are getting a connection refused error when trying to access the discovery URL. We have installed the lua-resty-socket module but we're amateurs at lua. Could you point us in the right direction to get openidc to use a proxy?

Question about redirect_uri_path

This issue is similar to issue #7

Once the Authentication provider redirects the page back. The browser with redirect_uri in this case (/secured) with all the scope, state and code parameters doesn't show anything. I am trying to redirect the user to Home page in this case its http://xyz.abc.com:8888. When I manually modify the url by removing the string starting from secured, it works.

Sometimes the redirection happens correctly to the Home page http://xyz.abc.com:8888 without /secured querystring.

Here is the url that is coming back after authentication
Initial Url:

http://xyz.abc.com:8888

Return Url:

http://xyz.abc.com:8888/secured?code=AQABAAIAAADRNYRQ3dhRSrm-4K-adpCJ7YXOwPP2_C_d2P7-QxX1uqmYzl1YXCS4i8Re3wSlYhmj7HNv-2s0_Y41P20yHNCfYJq6joqiMJ6C-Q3ot524UnRPBfjwFhpOlcr4gAA&state=107364f895c8f787fb28d&session_state=c8b0a1f4-fcee-4c98-9351-dfda1

Here is the conf file

    # authentication
    location ~ (\.cgi$|(^/(logout|secured)$)) {
        set $auth_email "";
        set $auth_realname "";
        access_by_lua '
          local opts = {
             -- the full redirect URI must be protected by this script and becomes:
             -- ngx.var.scheme.."://"..ngx.var.http_host..opts.redirect_uri_path
             -- unless the scheme is overridden using opts.redirect_uri_scheme or an X-Forwarded-Proto header in the incoming request
             redirect_uri_scheme = "http"
             redirect_uri_path = "/secured",
--          Need to make sure discovery path matches the response data
             discovery = "https://login.microsoftonline.com/../.well-known/openid-configuration",
             client_id = "xxxxx",
             client_secret = "xxxxxxxyyyyyy,
             --ssl_verify = "no"
             --authorization_params = { hd="pingidentity.com" },
             --scope = "openid email profile",
             iat_slack = 600,
             --logout_path = "/logout",
             token_endpoint_auth_method ="client_secret_post",
          }

Thanks in advance!

Create Lua Rocks profile

As mentioned in #13, someone in Ping Identity should create a profile on Lua Rocks so the current rock can be moved from my personal account to something a bit more official.

Question about redirect_url_path

Hi,

I have a bit difficulty to get a test configuration working with Openid Connect. Everything works fine until the auth provider redirecting pages back to the Nginx /redirect_uri. The browser in /redirect_uri page with all the scope, state, code parameters returns ERR_INVALID_RESPONSE.

I must have been missing something in the configuration. Do I need to add any special Nginx/Lua configuration the /redirect_uri URL or do I need to configure a Nginx location?

Thanks in advance!

Access to un-parsed id_token JWT for Kubernetes OIDC integration

I'm trying to use lua-resty-openidc as an OIDC RP in front of Kubernetes. Based on the Kuberentes docs, it looks like the oidc integration requires the original jwt id_token instead of the access_token. (kubernetes/website#1209). Would there be any issues with adding the unparsed json.id_token to the session data so I can set it in the Authorization Header when proxying to k8s?

Thanks!

attempt to concatenate field 'state' (a nil value)

This is working very well, except on about 10-15% of page loads (two different sites) typically after an initial successful login to the site, I am getting 500's which show up as attempt to concatenate field 'state' (a nil value) in the logs.

Here are two examples:

2016/01/10 08:41:18 [error] 20680#0: *198 lua entry thread aborted: runtime error: /opt/openresty/lualib/resty/openidc.lua:260: attempt to concatenate field 'state' (a nil value)
stack traceback:
coroutine 0:
    /opt/openresty/lualib/resty/openidc.lua: in function 'authenticate'
    access_by_lua(nginx.conf:232):11: in function <access_by_lua(nginx.conf:232):1>, client: IP_REDACTED, server: REDACTED.com, request: "GET /redirect_uri?state=1f3a05983ecad4f7c567cec9906ec613&code=4/xfw5ux-IejCSEcojEcgs-fcxqz1J6V5xF2lORuipuFw&authuser=0&hd=REDACTED.com&session_state=d9e845af3ee7ece2958d2c45dc3abf123b77a056..d752&prompt=none HTTP/1.1", host: "REDACTED.com", referrer: "https://REDACTED.com/jsp/common/login/Login.jsp;jsessionid=G1gFWSXVrTylsXdHkRTp9hRcmXKcyTfZWF8rzsmGLmpk76TwDlvj!-1484896595"
2016/01/10 07:51:27 [error] 20526#0: *14 lua entry thread aborted: runtime error: /opt/openresty/lualib/resty/openidc.lua:260: attempt to concatenate field 'state' (a nil value)
stack traceback:
coroutine 0:
    /opt/openresty/lualib/resty/openidc.lua: in function 'authenticate'
    access_by_lua(nginx.conf:188):11: in function <access_by_lua(nginx.conf:188):1>, client: IP_REDACTED, server: REDACTED.com, request: "GET /redirect_uri?state=4ff8e3db1e9490dd2e5c9e9106a211b5&code=4/W8OlE5WneQJRJ37QmKWUH_XO3EoCfGxDOKJ1FoVqZ-Y&authuser=0&hd=REDACTED.com&session_state=85ec1da20441f46f50edc2dd7b190bd96b7e4b4d..d98b&prompt=none HTTP/1.1", host: "REDACTED.com", referrer: "https://accounts.google.com/ServiceLogin?continue=https%3A%2F%2Faccounts.google.com%2Fo%2Foauth2%2Fv2%2Fauth%3Fscope%3Dopenid%2Bemail%2Bprofile%26response_type%3Dcode%26state%3D4ff8e3db1e8690dd2e5c9e9106a211b5%26redirect_uri%3Dhttps%3A%2F%2FREDACTED.com%2Fredirect_uri%26nonce%3Db58d3254d725632575716345f28b1a8b%26client_id%3DREDACTED.apps.googleusercontent.com%26hl%3Den%26from_login%3D1%26as%3D7e62210aa03c7ffe&ltmpl=popup&sacu=1&scc=1&passive=1209600&acui=0"

Is the client_secret for Google+ Signin and the secret for JWT the same thing?

I have a scenario where I want to use the redirect-based approach most of the time, but in one case I need to do CORS from JavaScript. If I create another endpoint using the JWT config, can I pass a jwt from the client and have lua-resty-openidc verify it with the client_secret? My signature algorithm is HS256. The client obtains the jwt token through a different path (which doesn't involve nginx).

OPM Question

Do you plan on adding this nifty module to OPM?
Just wondering because it would be nice to have.
Thanks!

"invalid token: No trusted certs loaded", client: 10.12.147.129

Hello,
I am using following code to verify the token from keycloak,
local json, err, access_token = require("resty.openidc").bearer_jwt_verify(utils.get_options(config, ngx))
but an error was returned "invalid token: No trusted certs loaded", can someone tell what certs needs to be load and how to load it? thanks

Clarify documentation around refresh_session_interval vs access_token_expires_in

The readme currently says:

    -- Refresh the user's id_token after 900 seconds without requiring re-authentication
    --refresh_session_interval = 900,
    ...
    --access_token_expires_in = 3600
    -- Default lifetime in seconds of the access_token if no expires_in attribute is present in the token 
    endpoint response.
       This plugin will silently renew the access_token once it's expired if refreshToken scope is present.

I interpreted this to mean:

  • If less than 900 seconds have passed, don't validate anything, reuse the existing session.
  • If between 900 and 3600 seconds have passed, silently refresh the id_token/access token server side using the refresh token, but with no client side redirects.
  • If >3600 seconds have passed, then redirect the client to the OP for re-authentication.

Whereas from reading the source the behaviour is actually:

  • If less than 900 seconds have passed, don't validate anything, reuse the existing session.
  • If >900 force refresh of the access token by redirecting the client to the OP with prompt = "none".
    (Presuming the access token returned from the OP doesn't have an expiry of its own which is shorter than 3600 seconds)

If I've interpreted the behaviour correctly, it would seem the refresh_session_interval description in the docs might be more accurately worded as:

Redirect the client to the OP (but with no authentication prompt) after 900 seconds to refresh their id_token and access token.

Also the access_token_expires_in description should probably read:

...
This plugin will silently renew the access_token once it's expired if refreshToken scope is present, so long as refresh_session_interval is not set.

Many thanks!

help w/ setting custom cookie

hi there, great library. i'm wondering if you could give me pointer to where i could modify the set cookie action. we've been using tornado to do this in the past, but this library + openresty would be much better and would align w/ more of the rest of our stack.

we have a few pages that house a dashboard on admin.prodz.imgix.com and the page makes ajax calls to an api at api.prodz.imgix.com. w/ tornado, we did a set cookie on prodz.imgix.com once google sent the user back to the redirect url. i read through the code, but it didn't jump out at me where the right edits would yield this behavior. mind giving me a push in the right direction?

not sure how to handle interaction_required

When using Google as an OP, I have been using opts.authorization_params.prompt = "none", in cases where I am reauthorizing a recently authorized user, but want to do so in a way that is seamless/invisible to the user.

This works well except in a few rare cases, where the user has beeb independently logged out of Google in the brief time between first authorization to my app, and the reauthorization attempt using opts.authorization_params.prompt = "none".

In those cases, Google sends back the following args (and only the following args) to the redirect url:

state=032b7b6bb5ba1daabe0abfa8eea75090  (for example)
&error_subtype=access_denied
&error=interaction_required

This, of course, throws the following error:

openidc.lua:256: authenticate(): unhandled request to the redirect_uri: /redirect_uri?state=032b7b6bb5ba1daabe0abfa8eea75090&error_subtype=access_denied&error=interaction_required

How would this best be handled? Ideally, I'd like to redirect back to Google with opts.authorization_params.prompt = nil so that the user can be prompted in this one case.

Is this kind of response something that maybe lua-resty-openidc could/should be prepared to handle?

lua-resty-jwt broken, prevents lua-resty-openidc install

0.1.10 of lua-resty-jwt is broken and is preventing the installation of lua-resty-openidc. I'm not sure the best solution here, as far as I can see there is no way to specify a version that should be excluded inside a range of versions inside a rockspec file. It should be possible to change the dependency version spec to:

"lua-resty-jwt >= 0.1.5, < 0.1.10",

but this would prevent using versions of lua-resty-jwt later than 0.1.10. You could revert the change when a fix to lua-resty-jwt is released? Anyway, not too sure what to do here, thoughts?

Support for JWT via discovery without x5c

In the available documentation Sample Configuration for OAuth 2.0 JWT Token Validation, there is a comment: -- The jwks endpoint must provide a x5c entry

I just hit an issue when trying to validate a token:

2017/02/10 09:44:04 [debug] 7#7: *2 [lua] openidc.lua:411: openidc_jwks(): Response data: {"keys":[{"kid":"YDAkh8zPFq5KdfnqtZZlpVG2fajlLzAcqh7BmV289JE","kty":"RSA","alg":"RS256","use":"sig","n":"qT3Lnddxydyj1-clZwcsFVisAEgglHMQbcJgozhlDC3vHFV2b_-xIqEEQtC56CgdPGe4LVwDBcNOp5a23OheVPzaTAdr2JwNqVg8Oeen0ba2ca-fBySXJyUEP2qmlIW5Ar7kexqRT-LhGCCf3iA3Bjs2o_PIH5Eu5V3AaubnZO1hqni7VVHpF0QdLmfVCBWK6FoxrEqedRuPFHLoX4GF3tUTk-drPoEwe3Tf_J6UICAp1QaRr-hGVyKsIf0gjru8ArzFcLfZuH21ZJJ6QWuFGCFFe90KCWC3LdTzyYB5byq3z915E9MeEH0zraTPCs0_OelzuzjpUVQQsy6BNJwEww","e":"AQAB"}]}
2017/02/10 09:44:04 [debug] 7#7: *2 lua resume returned 2
2017/02/10 09:44:04 [error] 7#7: *2 lua entry thread aborted: runtime error: /usr/local/openresty/luajit/share/lua/5.1/resty/openidc.lua:458: attempt to index local 'x5c' (a nil value)
stack traceback:
coroutine 0:
	/usr/local/openresty/luajit/share/lua/5.1/resty/openidc.lua: in function 'pem_from_jwk'
	/usr/local/openresty/luajit/share/lua/5.1/resty/openidc.lua:694: in function 'bearer_jwt_verify'
	access_by_lua(nginx.conf:73):22: in function <access_by_lua(nginx.conf:73):1>, client: 172.17.0.1, server: , request: "GET /api HTTP/1.1", host: "localhost:8082"

There is TODO entry to check the x5c length at https://github.com/pingidentity/lua-resty-openidc/blob/v1.3.0/lib/resty/openidc.lua#L457

Our provider implementation don't expose this key, and it seems that google certs also don't expose a x5c property in their certs:

I was wondering why x5c is a requirement for validation? I wouldn't like to hardcode the cert in the nginx.conf so I was trying to get the public cert via discovery. (Perhaps i'm missing something)

session state doesn't match state

I keep getting this error when trying to configure openidc

openidc.lua:294: authenticate(): state from argument: adf7c9545d2d880ec073c96b63da7a60 does not match state restored from session: nil

Note I have the latest version of lua-resty-session installed as I have read that the session_check_ssi has caused problems in the past... any ideas?

Question: how to configure logout redirect

Hello

I am trying to 'convert' a working mod_auth_openidc configuration to lua-resty-openidc.

Login is working, and so is logout, but I cannot seem to get the OP redirect to an application page after logout

With mod_auth_openidc I could do something like "callback?logout=<redirect url", but I can's find how to configure that using the combination of 'logout_path' and 'redirect_after_logout_uri'. I guess I need to specify it in redirect_after_logout_uri but that is not really working.
Perhaps the id_token_hint is missing or something. THis is automagically provided using mod_auth_openidc.

All tips warmly welcomed!

Tx

Peter

lua_ssl_trusted_certificate

This may be a daft question, but what certificate is this supposed to be?

lua_ssl_trusted_certificate /opt/local/etc/openssl/cert.pem

Clearly I am not using the right one, as this is what I see in the nginx error logs:

2016/01/10 07:12:36 [alert] 20456#0: *1 ignoring stale global SSL error (SSL: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt), client: IP_REDACTED, server: REDACTED.com, request: "GET / HTTP/1.1", host: "REDACTED.com"
2016/01/10 07:12:36 [error] 20456#0: *1 lua ssl certificate verify error: (20: unable to get local issuer certificate), client: IP_REDACTED, server: REDACTED.com, request: "GET / HTTP/1.1", host: "REDACTED.com"

If session does not match, redirect omits port

I'm using https://localhost:8443/ as a test server. It looks like a redirect on the error path drops the port.

  1. Set lua_code_cache off without setting an explicit session secret. This causes resty.session to choose a new secret for every request, and it will not be able to decode session cookies.
  2. Visit protected URL, get redirected to IdP
  3. Redirect back to callback URL
  4. Redirect to https://localhost/ with no port

Lua runtime error when the Host header is not set

I spotted this in the nginx error log (using lua-resty-openidc 1.3.2-1 and openresty 1.11.2.3):

2017/06/03 00:18:17 [error] 17499#17499: *76717 lua entry thread aborted: runtime error: /usr/share/lua/5.1/resty/openidc.lua:139: attempt to concatenate field 'http_host' (a nil value)
stack traceback:
coroutine 0:
        /usr/share/lua/5.1/resty/openidc.lua: in function 'openidc_get_redirect_uri'
        /usr/share/lua/5.1/resty/openidc.lua:173: in function 'openidc_authorize'
        /usr/share/lua/5.1/resty/openidc.lua:642: in function 'authenticate'
        .../local/openresty/nginx/conf/SNIP/openidc_layer.lua:27: in function <.../local/openresty/nginx/conf/SNIP/openidc_layer.lua:1>, client: SNIP, server: , request: "GET /scripts/nph-test-cgi?* HTTP/1.0"

Looks like someone was running a scanner against the site, which wasn't setting the Host header.

Presumably this should be null-checked before use?

Upload Docker image to Docker Hub

To be able to upload a Docker image of lua-resty-openidc, an organization and repository need to be set up on Docker Hub. When that's done, we should add a docker push step to the after_deploy section of the .travis.yml file, so a new Docker image is uploaded for every new tag that is built.

Add integration tests

It would be nice if some sort of integration tests were easily available in this repository, either as something created for lua-resty-openidc itself, as a Git submodule (if tests exist elsewhere) or just as a reference in the form of a link in the README.

I've been searching for some sort of official test suite for OAuth and OpenID Connect, but the only thing I've found is this, which requires setting up all kinds of user-dependent stuff on oauth.io and LinkedIn. It also didn't work after I went through the required steps, so I don't know if this is an achievable goal at all.

constant ssl errors

I'm sure this si not related to this lib, but I'm hoping to get some direction here. Nothing's changed on my proxy that I use this lib on that enables a general purpose google auth proxy, but I'm seeing these errors after 5-10 seconds after authenticating in the debug log and it's rendering this proxy unusable.

Any clues?

2016/10/25 20:03:18 [alert] 26406#0: ignoring stale global SSL error (SSL: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt)

Nginx OpenIDC with Keycloak 2.5 - Redirect Loop

Hello,

I have installed the lua-resty-openidc module in Nginx server and opensource keycloak server.

When I access any resource in Nginx it got redirect to Keycloak server for authentication. After authentication it redirect to Redirect URI and getting HTTP 500 server error. Am I missing something in the Redirect_Uri param?

http://nginxint.com:81/token?state=08621333464a7df9e995227744bc9d0a&code=grVpNleibVO_ogX5BhxEkgktuSEe83337xTrlvunkNo.458efbab-d1bf-4a98-89c0-9a958a3274b6

Returns HTTP 500 error.

Here is my opendic config.
access_by_lua '
local opts = {
-- the full redirect URI must be protected by this script and becomes:
-- ngx.var.scheme.."://"..ngx.var.http_host..opts.redirect_uri_path
-- unless the scheme is overridden using opts.redirect_uri_scheme or an X-Forwarded-Proto header in the incoming request
redirect_uri_path = "/token",
discovery = "http://keycloakint.com:8080/auth/realms/DCOS/.well-known/openid-configuration",
client_id = "NginxWS",
--client_secret = "<client_secret>"
--authorization_params = { hd="pingidentity.com" },
--scope = "openid email profile",
--iat_slack = 600,
--redirect_uri_scheme = "https",
--logout_path = "/logout",
--token_endpoint_auth_method = ["client_secret_basic"|"client_secret_post"],
--ssl_verify = "no"
}

JWT support for iat_slack/grace period

We ares using the openidc.jwt_verify function and would like add some grace period to the expiration of the tokens.

Currently there are two places where the expiration is checked:

  1. https://github.com/pingidentity/lua-resty-openidc/blob/e451ed7d076e44541740bb524389a9aa31d5ce69/lib/resty/openidc.lua#L823
    jwt:verify(opts.secret, access_token, ...)
  2. https://github.com/pingidentity/lua-resty-openidc/blob/e451ed7d076e44541740bb524389a9aa31d5ce69/lib/resty/openidc.lua#L842
    if json.exp and json.exp < ngx.time() then

I tried setting grace periods in for both of these checks and they seem redundant to me. What is the reason/edge case for 2. Could the check just be removed?

Ways to set a grace period in 1. and 2.

  1. pass {lifetime_grace_period = opts.iat_slack} as parameter Probably should just be passed in by the caller (not this library). so no change is necessary
  2. We would need to add the grace period to the check:
-- check the token expiry
  if json then
    if json.exp and json.exp + slack < ngx.time() then

Checking in this way is already being done at another place in the code: https://github.com/pingidentity/lua-resty-openidc/blob/e451ed7d076e44541740bb524389a9aa31d5ce69/lib/resty/openidc.lua#L106

Solutions to this issue:
a) remove the check in L842 completely
b) add the iat_slack to the if-clause
c) ?

What are your thoughts on this?

'check session only' or 'guest user' feature

Let me know if you are familiar with this mode of operation.

I have an application that expects REMOTE_USER from the web server. It checks the REMOTE_USER against permissions on individual pages within the application. If a page is private, the application returns 401 or 403 depending on whether REMOTE_USER is set. The web server converts 401 to a redirect to the login page.

In other words, the bulk of the application would pass a flag to openidc.authenticate() to skip the openidc_authorize() call. Instead of doing a redirect, openidc.authenticate() would return 'user not logged in': https://github.com/pingidentity/lua-resty-openidc/blob/master/lib/resty/openidc.lua#L635

The location for the login page would of course continue to do the redirect when the user was not logged in.

INTERNAL SERVER ERROR 500.

Hello there,

I get an internal server error with nothing in my logs, so it is kinda hard to debug.
My config file works without the require("resty.openidc") line.

As soon as I add this line, the whole thing crashed.

I setted : lua_package_path '/usr/local/openresty/lualib/resty/?.lua;;';
I got the openidc.lua file in the package path above.

I can't see what's wrong ..

I am quite new to this, so it might be pretty simple, I am still not sure the way I add package is the good one ..

Please help me.
Thanks in advance for your time,
Twinko5

question about session cookie expiration

I am finding that some browsers don't expire session cookies even when the browser is quit an relaunched. I don't want them to persist that long.

I tried these lua-resty-session settings:

set $session_cookie_renew 600;
set $session_cookie_lifetime 3600;

But the cookie that session cookie set by lua-resty-openidc still seems to show "session" expiration, and the above settings seem to have no effect.

Am I misusing these settings, or is there perhaps a better approach to shorter term invalidation and/or expiration of lua-resty-openidc sessions?

"require claim" support?

Is there any way to specify if a claim is required, for example if a person has a certain value for role?

Misleading function signature openidc_authorization_response

Hi,
I just tried to set up the project and ran into an error, that wasn't returned correctly. Below is my try at root cause analysis:
openidc_authorization_response returns ngx.redirect(session.data.original_url), session.
https://github.com/pingidentity/lua-resty-openidc/blob/master/lib/resty/openidc.lua#L363
The first ngx.redirect is nil and the second is the session-table. This matches the usual nil, err guideline and is promptly misunderstood by the caller https://github.com/pingidentity/lua-resty-openidc/blob/master/lib/resty/openidc.lua#L636 .
Thereby the example code in the README.md would contain the session as err and ngx.say would probably fail because session is a table:
https://github.com/pingidentity/lua-resty-openidc

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

if err then
  ngx.status = 500
  ngx.say(err)
  ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end

https://github.com/pingidentity/lua-resty-openidc/blob/master/lib/resty/openidc.lua#L363

I have a working solution, but I am not yet sure it does the right thing in all cases and will hopefully make a pull-request eventually.

Add support for passing options to resty.session

I'd like to add support for resty.openidc passing resty.session options into resty.session in order to set session settings.

In my specific case I'm doing two OIDC code flows against different OPs and need to establish distinct OIDC sessions with each OP. Consequently I need to be able to set the resty.session name.

Issue in using UAA as OpenId provider

When using UAA the url openid configuration URL of UAA http:///uaa/.well-known/openid-configuration returns a JSON where the issuer field has value http:///uaa/oauth/token

This fails the validation in the openidc.lua and gives a error "issuer field in Discovery data does not match URL". Instead of checking for the full URL if the validation is changed to checking only for the host name then we would be able to use the script with OpenId as well.

access_token API: error refreshing token

Hello,

Sorry, this is an error I introduced when creating the access_token API.

Whenever I call access_token (with an expired accessToken), the lua module tries to refresh the token, but without having initialized the token_endpoint URL.

I get a :

2017/05/28 20:06:42 [debug] 7#7: *13 [lua] openidc.lua:563: access_token(): refreshing expired access_token: 3c68223ff0994bcfb633776dc47847 with: 8bc1d56b7bad2b4b91f516aa6c0caf1
2017/05/28 20:06:42 [debug] 7#7: *13 [lua] openidc.lua:227: openidc_call_token_endpoint(): client_secret_basic: authorization header 'Basic bTJEaXcwUkxxSXF0aHc6REtxeURjQXVyZ0Jpc2NDYnVSUUZmZw=='
2017/05/28 20:06:42 [debug] 7#7: *13 [lua] openidc.lua:236: openidc_call_token_endpoint(): request body for token endpoint call: refresh_token=8bc1d56b7bad2b4b91f516aa6c0caf1&grant_type=refresh_token&scope=openid%20refreshToken
2017/05/28 20:06:42 [error] 7#7: *13 lua entry thread aborted: runtime error: /usr/local/openresty/luajit/share/lua/5.1/resty/http.lua:191: bad argument #1 to 'ngx_re_match' (string expected, got nil)
stack traceback:
coroutine 0:
 [C]: in function 'ngx_re_match'
 /usr/local/openresty/luajit/share/lua/5.1/resty/http.lua:191: in function 'parse_uri'
 /usr/local/openresty/luajit/share/lua/5.1/resty/http.lua:720: in function 'request_uri'
 /opt/lua-resty-openidc/lib/resty/openidc.lua:239: in function 'openidc_call_token_endpoint'
 /opt/lua-resty-openidc/lib/resty/openidc.lua:571: in function 'access_token'
 access_by_lua(nginx.conf:146):21: in function <access_by_lua(nginx.conf:146):1>, client: 172.31.0.1, server: localhost, request: "GET /api HTTP/1.1", host: "iam"
172.31.0.1 - - [28/May/2017:20:06:42 +0000] "GET /api HTTP/1.1" 500 541 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/58.0.3029.110 Chrome/58.0.3029.110 Safari/537.36"

nginx.conf extract:

    location / {

      access_by_lua '

      local opts = {
        redirect_uri_path = "/redirect_uri",
        discovery = "http://iam:9080/oidc/.well-known/openid-configuration",
        client_id = "m2Diw0RLqIqthw",
        client_secret = "DKqyDcAurgBiscCbuRQFfg",
        scope = "openid refreshToken",
        iat_slack = 600,
        redirect_uri_scheme = "http",
        logout_path = "/api/logout",
        token_endpoint_auth_method = "client_secret_basic",
        ssl_verify = "no"
      }

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

      if err then
      ngx.status = 500
      ngx.say(err)
      ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
      end
      ';

      root   html;
      index  index.html index.htm;
    }


    location /api {

      access_by_lua '

      local opts = {
        redirect_uri_path = "/redirect_uri",
        discovery = "http://iam:9080/oidc/.well-known/openid-configuration",
        client_id = "m2Diw0RLqIqthw",
        client_secret = "DKqyDcAurgBiscCbuRQFfg",
        scope = "openid refreshToken",
        iat_slack = 600,
        redirect_uri_scheme = "http",
        logout_path = "/api/logout",
        token_endpoint_auth_method = "client_secret_basic",
        ssl_verify = "no"
      }

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

      if err then
      ngx.status = 401
      ngx.say(err)
      ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
      end

      -- set Authorization header to AT
      if access_token then
      ngx.req.set_header("Authorization", "Bearer " .. access_token)
      end
      ';

      proxy_pass http://192.168.0.13:10001/;
    }

With the previous configuration, the problem happens whenever I call http://iam/api and the token is expired.

Mismatched state error

I'm getting an error related to sessions:

2017/03/23 01:01:26 [error] 11317#0: *1 [lua] openidc.lua:294: authenticate(): state from argument: 4ba273aded3294e5684674a39fe778cce23f84ead624db4b does not match state restored from session: a60dd9fb58feda4d72eee2af52a79789, client: 10.0.1.26, server: <snip>.com, request: "GET /auth/google/callback?state=4ba273aded3294e5684674a39fe778cce23f84ead624db4b&code=4/PvFKMkF62CiOupt3aRQC3PHanzlvV0NDh2dJnMwqYeo HTTP/1.1", host: "<snip>.com"

My relevant Nginx config is:

location / {
    set $session_secret <snip>;
    access_by_lua '

      local opts = {
         -- the full redirect URI must be protected by this script and becomes:
         -- ngx.var.scheme.."://"..ngx.var.http_host..opts.redirect_uri_path
         -- unless the scheme is overridden using opts.redirect_uri_scheme or an X-Forwarded-Proto header in the incoming request
         redirect_uri_path = "/auth/google/callback",
         discovery = "https://accounts.google.com/.well-known/openid-configuration",
         client_id = "snip.apps.googleusercontent.com",
         client_secret = "snip",
         authorization_params = { hd="snip.com" },
         --scope = "openid email profile",
         --iat_slack = 600,
         --redirect_uri_scheme = "https",
         --logout_path = "/logout",
         --token_endpoint_auth_method = ["client_secret_basic"|"client_secret_post"],
         ssl_verify = "no"
      }

      -- call authenticate for OpenID Connect user authentication
      local res, err = require("resty.openidc").authenticate(opts)

      if err then
        ngx.status = 500
        ngx.say(err)
        ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
      end

      -- at this point res is a Lua table with 3 keys:
      --   id_token    : a Lua table with the claims from the id_token (required)
      --   access_token: the access token (optional)
      --   user        : a Lua table with the claims returned from the user info endpoint (optional)

      --if res.id_token.hd ~= "pingidentity.com" then
      --  ngx.exit(ngx.HTTP_FORBIDDEN)
      --end

      if res.user.email ~= "@snip.com" then
        -- ngx.exit(ngx.HTTP_FORBIDDEN)
      end

      -- set headers with user info (overwriting any existing!)
      ngx.req.set_header("X-USER", res.id_token.sub)
    ';

    try_files $uri @app;
    break;
  }

I did try set $session_secret snip; in the server{} block, and also memcached for sessions but stil get this error. The environment is on AWS behind an ELB that does SSL termination and forwards the decrypted traffic to nginx on port 80. For the time being I've limited the backend to one server which is running Nginx with worker_processes 1;. I also tried turning off the lua code cache, but to no avail.

Any help with this would be welcome.

Question on JWT Token Validation

I am trying to validate my access_token using https://github.com/pingidentity/lua-resty-openidc#sample-configuration-for-oauth-20-jwt-token-validation-1. Somehow I wasn't able to succeed. My goal is to protect my apis using this approach. Here is my configuration. Am I missing something here. I am getting 2 issues here. Can someone tell me where I am going wrong. I was able to verify the signature using jwt.io

  1. With the below configuration, I am getting openidc_discover(): accessing discovery url (https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration) failed: 20: unable to get local issuer certificate, client:

  2. When I added secret (public key string from my secret.crt file) I am getting "reason":"Verification failed","raw_header":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6InowMzl6ZHNGdWl6cEJmQlZLMVRuMjVRSFlPMCIs

location /api {
      access_by_lua '
          local opts = {
            -- The jwks endpoint must provide a x5c entry
   discovery = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration"
          }
          -- call bearer_jwt_verify for OAuth 2.0 JWT validation
          local res, err = require("resty.openidc").bearer_jwt_verify(opts)
..
..
    }
  }
}

I am sending Curl request with authorization token to test if my /api is secured or not like below
curl -i http://myserver.com/api/index.html -H 'Authorization: bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6InowMzl6ZHNGdWl6cEJmQlZLMVRuMjVRSFlPMCIs'

Build Error Failed installing lib/resty/openidc.lua

Hi Ping Identity,
I'm just trying to set up one of our Ubuntu 14.04 Servers running Nginx up to use OpenID Connect.

It's a plain installation, I've tried installing OpenRusty (I wasn't sure if this was a requirement) and luarocks.

I can only get as far in the installation as:
lua-resty-http 0.08-0 is now built and installed in /usr/local/ (license: 2-clause BSD)
cp: cannot stat ‘lib/resty/openidc.lua’: No such file or directory

Error: Build error: Failed installing lib/resty/openidc.lua in /usr/local/lib/luarocks/rocks/lua-resty-openidc/1.2.1-1/lua/resty

Any ideas how to get past this part? I've followed the information in the readme.md

Many thanks.

Where should I put OIDCCryptoPassphrase ?

I am migrating from the mod_auth_openidc Apache plugin to lua-resty-openidc with Nginx. In Apache I had my OAuth2 provider JsonWebToken Signature Algorithm set to RS256. With this setting the lua-resty-openidc module is in a redirect loop with the OAuth2 provider. I changed the algorithm to HS256 and now the first request authenticates fine but subsequent requests trigger authentication redirect (it appears session is not being kept). In the logs I have:

ignoring stale global SSL error (SSL: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt)

and I saw your response to #26 so I tried setting:

set $session_cipher none;

no difference, first request is authenticated, subsequent requests trigger authentication.

Also tried:

set $session_secret 623q4hR325t36VsCD3g567922IC0073T;

and no difference.

I am trying to move away from mod_auth_openidc because websockets are not working when both Apache & Nginx are involved (I need some Nginx-specific features). In Apache I had OIDCCryptoPassphrase and I am not sure where to put this variable in lua-resty-openidc or if it is related to the problem at all...

Check issued-at timestamp is failing

The lua-resty-openidc plugin is failing for us on simple requests. The problem is likely caused by differences in time synch and/or latency between our Ping server and our app servers running on AWS & Verizon clouds.

We're seeing the following message in our logs:

2015/09/29 19:30:56 [error] 18972#0: *4 [lua] openidc.lua:90: openidc_validate_id_token(): token is not valid yet: id_token.iat=1443554983, os.time()=1443555056, client: <client_ip>, server: , request: "GET /redirect_uri?state=&code= HTTP/1.1", host: "<server_ip:port>"

Increasing the value in the script to 1000 fixes the problem for us. Is there a reason for the hard-coded 10ms value in the script? Is it reasonable to increase that value or to parameterize it so that implementations can override in config files?

why `exp` is special?

The code as blow:

   if json then
      local expiry_claim = opts.expiry_claim or "expires_in"
      local ttl = json[expiry_claim]
      if expiry_claim ~= "exp" then --https://tools.ietf.org/html/rfc7662#section-2.2
        ttl = ttl - ngx.time()
      end
      openidc_cache_set("introspection", access_token, cjson.encode(json), ttl)
    end

I check https://tools.ietf.org/html/rfc7662#section-2.2 link, that's as blow:

exp
      OPTIONAL.  Integer timestamp, measured in the number of seconds
      since January 1 1970 UTC, indicating when this token will expire,
      as defined in JWT [RFC7519].

The exp is integer timestamp, isn't seconds from now.

Interaction required error from token end point

I am getting this issue suddenly. not sure why I am getting this. We are working with Microsoft azure OAuth. Can someone tell how you fixed this issue? I referred to issue #8 where it was fixed by redirecting the user to original url. Can you paste the sample code that can be used in nginx.conf file to fix this ?

The below error is coming from openidc_call_token_endpoint method. We should redirect the user to Microsoft auth page whenever interaction_required error is seen, once he completes the additional authentication then bring him back.

response indicates failure, status=400, body={"error":"interaction_required","error_description":"AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access '00000002-0000-0000-c000-000000000000'.\r\nTrace ID: 5889afab-8ce9-40da-b5fb-c76b4f420000\r\nCorrelation ID: 215515f1-8f10-4ed6-b4a8-fda77d259dcc\r\nTimestamp: 2017-05-09 23:30:44Z","error_codes":[50076],

We have MFA setup within our org. during the initial authentication microsoft automatically takes to OTP page and completes the authentication but not sure from where this interaction_ required error is hit again. I have 2 machines in different locations, the flow is working fine from one machine where OTP page is shown, but it is not working from other location where OTP page is not shown. Hope my explanation is clear

Infinite authentication loop

With 1.2.3 the encrypted id_token is saved in the session data (#29). This pushes the session cookie size above 4 kb with e.g. Azure Active Directory and thus the cookie will not be stored by most browsers.

Possible workarounds are to use a different session store. Using a gzip based session encoder might also be an option.

Question about reverse proxy

I use lua-resty-openidc for protecting some services in a docker environment. I’ve defined Nginx as a reverse proxy for protected Apps, everything works fine (in a really nice way).
Recently I would like to try the reverse proxy traefik.

Now I tested the reverse proxy traefik.
So, I put traefik in front of Nginx and traefik manage the ssl stuff. By this way Nginx is just here for protecting Apps and manage the OpenId Connect authentication.
But with this configuration it did not work.

server { 
    listen       80;
    server_name  "kibana.acme.org";
    access_log off;

    location / {
        access_by_lua '
                local cjson = require "cjson"

                function inTable(tbl, item)
                    for key, value in pairs(tbl) do
                        if value == item then return key end
                    end
                    return false
                end

               local opts = {
                    redirect_uri_path = "/redirect_uri",
                    discovery = os.getenv("DISCOVERY"),
                    client_id = os.getenv("CLIENT_ID"),
                    client_secret = os.getenv("CLIENT_SECRET"),
                    scope = os.getenv("CLIENT_SCOPE"),
                    ssl_verify = os.getenv("SSL_VERIFY")
                }

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

                if err then
                    ngx.status = 500
                    ngx.say(err)
                    ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
                end

                if inTable(res.user.role, "admin_kibana") == false then
                  ngx.exit(ngx.HTTP_FORBIDDEN)
                end
            ';

        proxy_pass         http://to_elk_kibana:5601;
    }
}

Another strange thing I’ve seen during my tests. Each time the browser makes a GET request on a protected App, lua-resty-openidc make a call to the OpenId Connect Provider. I don’t know if it’s a normal behavior or a settings mistake?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.