nginx
nginx/nginx-jwt.lua
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.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/nginx-jwt.lua Mon Jun 22 00:42:40 2015 -0400 1.3 @@ -0,0 +1,86 @@ 1.4 +local jwt = require "resty.jwt" 1.5 +local cjson = require "cjson" 1.6 +local basexx = require "basexx" 1.7 +local secret = os.getenv("JWT_SECRET") 1.8 + 1.9 +assert(secret ~= nil, "Environment variable JWT_SECRET not set") 1.10 + 1.11 +if os.getenv("JWT_SECRET_IS_BASE64_ENCODED") == 'true' then 1.12 + -- convert from URL-safe Base64 to Base64 1.13 + local r = #secret % 4 1.14 + if r == 2 then 1.15 + secret = secret .. "==" 1.16 + elseif r == 3 then 1.17 + secret = secret .. "=" 1.18 + end 1.19 + secret = string.gsub(secret, "-", "+") 1.20 + secret = string.gsub(secret, "_", "/") 1.21 + 1.22 + -- convert from Base64 to UTF-8 string 1.23 + secret = basexx.from_base64(secret) 1.24 +end 1.25 + 1.26 +local M = {} 1.27 + 1.28 +function M.auth(claim_specs) 1.29 + -- strip our headers to avoid spoofing 1.30 + ngx.req.clear_header("User-Id") 1.31 + ngx.req.clear_header("Scopes") 1.32 + ngx.req.clear_header("JWT-Scope") 1.33 + ngx.req.clear_header("JWT-Issuer") 1.34 + ngx.req.clear_header("JWT-Subject") 1.35 + ngx.req.clear_header("JWT-Expiration-Time") 1.36 + ngx.req.clear_header("JWT-Not-Before") 1.37 + ngx.req.clear_header("JWT-Issued-At") 1.38 + 1.39 + -- require Authorization request header 1.40 + local auth_header = ngx.var.http_Authorization 1.41 + 1.42 + if auth_header == nil then 1.43 + ngx.log(ngx.INFO, "No Authorization header") 1.44 + -- ngx.exit(ngx.HTTP_UNAUTHORIZED) 1.45 + return 1.46 + else 1.47 + ngx.log(ngx.INFO, "Authorization: " .. auth_header) 1.48 + 1.49 + -- require Bearer token 1.50 + local _, _, token = string.find(auth_header, "Bearer%s+(.+)") 1.51 + 1.52 + if token == nil then 1.53 + ngx.log(ngx.INFO, "Missing token") 1.54 + -- ngx.exit(ngx.HTTP_UNAUTHORIZED) 1.55 + return 1.56 + else 1.57 + ngx.log(ngx.INFO, "Token: " .. token) 1.58 + 1.59 + -- require valid JWT 1.60 + local jwt_obj = jwt:verify(secret, token, 60) 1.61 + if jwt_obj.verified == false then 1.62 + if string.find(jwt_obj.reason, "expired at") ~= nil then 1.63 + ngx.status = ngx.HTTP_UNAUTHORIZED 1.64 + ngx.say('{"error": "access_denied", "header": "authorization"}') 1.65 + return ngx.exit(ngx.HTTP_UNAUTHORIZED) 1.66 + else 1.67 + ngx.log(ngx.WARN, "Invalid token: ".. jwt_obj.reason) 1.68 + -- ngx.exit(ngx.HTTP_UNAUTHORIZED) 1.69 + end 1.70 + return 1.71 + else 1.72 + ngx.log(ngx.INFO, "JWT: " .. cjson.encode(jwt_obj)) 1.73 + 1.74 + -- write the headers 1.75 + ngx.req.set_header("User-Id", jwt_obj.payload.sub) 1.76 + ngx.req.set_header("Scopes", jwt_obj.payload.scope) 1.77 + ngx.req.set_header("JWT-Scope", jwt_obj.payload.scope) 1.78 + ngx.req.set_header("JWT-Issuer", jwt_obj.payload.iss) 1.79 + ngx.req.set_header("JWT-Subject", jwt_obj.payload.sub) 1.80 + ngx.req.set_header("JWT-Expiration-Time", jwt_obj.payload.exp) 1.81 + ngx.req.set_header("JWT-Not-Before", jwt_obj.payload.nbf) 1.82 + ngx.req.set_header("JWT-Issued-At", jwt_obj.payload.iat) 1.83 + return 1.84 + end 1.85 + end 1.86 + end 1.87 +end 1.88 + 1.89 +return M