First basic pass at JWT auth.
Mostly just a fork of https://github.com/ficusio/openresty, with a few twists:
* We've narrowed down some of the configuration options, and we're passing more
headers (essentially exposing all the data in the JWT as headers).
* We no longer automatically return a 401 unauthorized if the JWT verification
fails; we just don't assign it the headers. The consuming service can decide
whether or not they want to accept the request.
* We automatically fail the verification of a JWT if the token has expired in
the last minute (or shouldn't be used for the next minute). If the token has
expired, we return a 401 that our clients can catch and use a refresh token
automatically from. If the token can't be used for another minute, we quietly
just refuse to add auth headers to the request.
1 local jwt = require "resty.jwt"
2 local cjson = require "cjson"
3 local basexx = require "basexx"
4 local secret = os.getenv("JWT_SECRET")
6 assert(secret ~= nil, "Environment variable JWT_SECRET not set")
8 if os.getenv("JWT_SECRET_IS_BASE64_ENCODED") == 'true' then
9 -- convert from URL-safe Base64 to Base64
12 secret = secret .. "=="
14 secret = secret .. "="
16 secret = string.gsub(secret, "-", "+")
17 secret = string.gsub(secret, "_", "/")
19 -- convert from Base64 to UTF-8 string
20 secret = basexx.from_base64(secret)
25 function M.auth(claim_specs)
26 -- strip our headers to avoid spoofing
27 ngx.req.clear_header("User-Id")
28 ngx.req.clear_header("Scopes")
29 ngx.req.clear_header("JWT-Scope")
30 ngx.req.clear_header("JWT-Issuer")
31 ngx.req.clear_header("JWT-Subject")
32 ngx.req.clear_header("JWT-Expiration-Time")
33 ngx.req.clear_header("JWT-Not-Before")
34 ngx.req.clear_header("JWT-Issued-At")
36 -- require Authorization request header
37 local auth_header = ngx.var.http_Authorization
39 if auth_header == nil then
40 ngx.log(ngx.INFO, "No Authorization header")
41 -- ngx.exit(ngx.HTTP_UNAUTHORIZED)
44 ngx.log(ngx.INFO, "Authorization: " .. auth_header)
46 -- require Bearer token
47 local _, _, token = string.find(auth_header, "Bearer%s+(.+)")
50 ngx.log(ngx.INFO, "Missing token")
51 -- ngx.exit(ngx.HTTP_UNAUTHORIZED)
54 ngx.log(ngx.INFO, "Token: " .. token)
57 local jwt_obj = jwt:verify(secret, token, 60)
58 if jwt_obj.verified == false then
59 if string.find(jwt_obj.reason, "expired at") ~= nil then
60 ngx.status = ngx.HTTP_UNAUTHORIZED
61 ngx.say('{"error": "access_denied", "header": "authorization"}')
62 return ngx.exit(ngx.HTTP_UNAUTHORIZED)
64 ngx.log(ngx.WARN, "Invalid token: ".. jwt_obj.reason)
65 -- ngx.exit(ngx.HTTP_UNAUTHORIZED)
69 ngx.log(ngx.INFO, "JWT: " .. cjson.encode(jwt_obj))
72 ngx.req.set_header("User-Id", jwt_obj.payload.sub)
73 ngx.req.set_header("Scopes", jwt_obj.payload.scope)
74 ngx.req.set_header("JWT-Scope", jwt_obj.payload.scope)
75 ngx.req.set_header("JWT-Issuer", jwt_obj.payload.iss)
76 ngx.req.set_header("JWT-Subject", jwt_obj.payload.sub)
77 ngx.req.set_header("JWT-Expiration-Time", jwt_obj.payload.exp)
78 ngx.req.set_header("JWT-Not-Before", jwt_obj.payload.nbf)
79 ngx.req.set_header("JWT-Issued-At", jwt_obj.payload.iat)