Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,66 @@ lAc5Csj0o5Q+oEhPUAVBIF07m4rd0OvAVPOCQ2NJhQSL1oWASbf+fg==
}
```

## Sample Configuration for OAuth 2.0 JWT Token Validation

Sample `nginx.conf` configuration for verifying Bearer JWT Access Tokens against a OpenID Connect Discovery endpoint.
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 introspection 10m;
# cache for jwks metadata documents
lua_shared_dict discovery 1m;

server {
listen 8080;

location /api {

access_by_lua '

local opts = {
-- The jwks endpoint must provide a x5c entry
-- discovery = "https://accounts.google.com/.well-known/openid-configuration",
}

-- 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 JSON
-- payload in the JWT token

--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.
Expand Down
119 changes: 109 additions & 10 deletions lib/resty/openidc.lua
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,76 @@ local function openidc_discover(url, ssl_verify)
return json, err
end

local function openidc_jwks(url, ssl_verify)
ngx.log(ngx.DEBUG, "In openidc_jwks - URL is "..url)

local json, err
local v = openidc_cache_get("jwks", url)
if not v then

ngx.log(ngx.DEBUG, "JWKS data not in cache. Making call to jwks endpoint")
-- make the call to the jwks endpoint
local httpc = http.new()
local res, error = httpc:request_uri(url, {
ssl_verify = (ssl_verify ~= "no")
})
if not res then
err = "accessing jwks url ("..url..") failed: "..error
ngx.log(ngx.ERR, err)
else
ngx.log(ngx.DEBUG, "Response data: "..res.body)
json, err = openidc_parse_json_response(res)
if json then
openidc_cache_set("jwks", url, cjson.encode(json), 24 * 60 * 60)
end
end

else
json = cjson.decode(v)
end

return json, err
end

local function split_by_chunk(text, chunkSize)
local s = {}
for i=1, #text, chunkSize do
s[#s+1] = text:sub(i,i+chunkSize - 1)
end
return s
end

local function get_jwk (keys, kid)
for _, value in pairs(keys) do
if value.kid == kid then
return value
end
end

return nil
end

local function pem_from_jwk (opts, kid)
local cache_id = opts.discovery.jwks_uri .. '#' .. kid
local v = openidc_cache_get("jwks", cache_id)

if v then
return v
end

local jwks, err = openidc_jwks(opts.discovery.jwks_uri, opts.ssl_verify)
if err then
return nil, err
end

local x5c = get_jwk(jwks.keys, kid).x5c
-- TODO check x5c length
local chunks = split_by_chunk(ngx.encode_base64(openidc_base64_url_decode(x5c[1])), 64)
local pem = "-----BEGIN CERTIFICATE-----\n" .. table.concat(chunks, "\n") .. "\n-----END CERTIFICATE-----"
openidc_cache_set("jwks", cache_id, pem, 24 * 60 * 60)
return pem
end

local openidc_transparent_pixel = "\137\080\078\071\013\010\026\010\000\000\000\013\073\072\068\082" ..
"\000\000\000\001\000\000\000\001\008\004\000\000\000\181\028\012" ..
"\002\000\000\000\011\073\068\065\084\120\156\099\250\207\000\000" ..
Expand Down Expand Up @@ -587,25 +657,39 @@ function openidc.introspect(opts)
end

-- main routine for OAuth 2.0 JWT token validation
function openidc.bearer_jwt_verify(opts)

function openidc.jwt_verify(access_token, opts)
local err
local json

-- get the access token from the request
local access_token, err = openidc_get_bearer_access_token(opts)
if access_token == nil then
return nil, err
end

ngx.log(ngx.DEBUG, "access_token: ", access_token)

-- see if we've previously cached the validation result for this access token
local v = openidc_cache_get("introspection", access_token)
if not v then

-- do the verification first time
local jwt = require "resty.jwt"

-- No secret given try getting it from the jwks endpoint
if not opts.secret and opts.discovery then
ngx.log(ngx.DEBUG, "bearer_jwt_verify using discovery.")
opts.discovery, err = openidc_discover(opts.discovery, opts.ssl_verify)
if err then
return nil, err
end

-- We decode the token twice, could be saved
local jwt_obj = jwt:load_jwt(access_token, nil)

if not jwt_obj.valid then
return nil, "invalid jwt"
end

opts.secret, err = pem_from_jwk(opts, jwt_obj.header.kid)

if opts.secret == nil then
return nil, err
end
end

json = jwt:verify(opts.secret, access_token)

ngx.log(ngx.DEBUG, "jwt: ", cjson.encode(json))
Expand Down Expand Up @@ -634,4 +718,19 @@ function openidc.bearer_jwt_verify(opts)
return json, err
end

function openidc.bearer_jwt_verify(opts)
local err
local json

-- get the access token from the request
local access_token, err = openidc_get_bearer_access_token(opts)
if access_token == nil then
return nil, err
end

ngx.log(ngx.DEBUG, "access_token: ", access_token)

return openidc.jwt_verify(access_token, opts)
end

return openidc