nginx

Paddy 2015-06-22 Child:ac9c19126939

0:68478c1bddde Go to Latest

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.

History
paddy@0 1 local jwt = require "resty.jwt"
paddy@0 2 local cjson = require "cjson"
paddy@0 3 local basexx = require "basexx"
paddy@0 4 local secret = os.getenv("JWT_SECRET")
paddy@0 5
paddy@0 6 assert(secret ~= nil, "Environment variable JWT_SECRET not set")
paddy@0 7
paddy@0 8 if os.getenv("JWT_SECRET_IS_BASE64_ENCODED") == 'true' then
paddy@0 9 -- convert from URL-safe Base64 to Base64
paddy@0 10 local r = #secret % 4
paddy@0 11 if r == 2 then
paddy@0 12 secret = secret .. "=="
paddy@0 13 elseif r == 3 then
paddy@0 14 secret = secret .. "="
paddy@0 15 end
paddy@0 16 secret = string.gsub(secret, "-", "+")
paddy@0 17 secret = string.gsub(secret, "_", "/")
paddy@0 18
paddy@0 19 -- convert from Base64 to UTF-8 string
paddy@0 20 secret = basexx.from_base64(secret)
paddy@0 21 end
paddy@0 22
paddy@0 23 local M = {}
paddy@0 24
paddy@0 25 function M.auth(claim_specs)
paddy@0 26 -- strip our headers to avoid spoofing
paddy@0 27 ngx.req.clear_header("User-Id")
paddy@0 28 ngx.req.clear_header("Scopes")
paddy@0 29 ngx.req.clear_header("JWT-Scope")
paddy@0 30 ngx.req.clear_header("JWT-Issuer")
paddy@0 31 ngx.req.clear_header("JWT-Subject")
paddy@0 32 ngx.req.clear_header("JWT-Expiration-Time")
paddy@0 33 ngx.req.clear_header("JWT-Not-Before")
paddy@0 34 ngx.req.clear_header("JWT-Issued-At")
paddy@0 35
paddy@0 36 -- require Authorization request header
paddy@0 37 local auth_header = ngx.var.http_Authorization
paddy@0 38
paddy@0 39 if auth_header == nil then
paddy@0 40 ngx.log(ngx.INFO, "No Authorization header")
paddy@0 41 -- ngx.exit(ngx.HTTP_UNAUTHORIZED)
paddy@0 42 return
paddy@0 43 else
paddy@0 44 ngx.log(ngx.INFO, "Authorization: " .. auth_header)
paddy@0 45
paddy@0 46 -- require Bearer token
paddy@0 47 local _, _, token = string.find(auth_header, "Bearer%s+(.+)")
paddy@0 48
paddy@0 49 if token == nil then
paddy@0 50 ngx.log(ngx.INFO, "Missing token")
paddy@0 51 -- ngx.exit(ngx.HTTP_UNAUTHORIZED)
paddy@0 52 return
paddy@0 53 else
paddy@0 54 ngx.log(ngx.INFO, "Token: " .. token)
paddy@0 55
paddy@0 56 -- require valid JWT
paddy@0 57 local jwt_obj = jwt:verify(secret, token, 60)
paddy@0 58 if jwt_obj.verified == false then
paddy@0 59 if string.find(jwt_obj.reason, "expired at") ~= nil then
paddy@0 60 ngx.status = ngx.HTTP_UNAUTHORIZED
paddy@0 61 ngx.say('{"error": "access_denied", "header": "authorization"}')
paddy@0 62 return ngx.exit(ngx.HTTP_UNAUTHORIZED)
paddy@0 63 else
paddy@0 64 ngx.log(ngx.WARN, "Invalid token: ".. jwt_obj.reason)
paddy@0 65 -- ngx.exit(ngx.HTTP_UNAUTHORIZED)
paddy@0 66 end
paddy@0 67 return
paddy@0 68 else
paddy@0 69 ngx.log(ngx.INFO, "JWT: " .. cjson.encode(jwt_obj))
paddy@0 70
paddy@0 71 -- write the headers
paddy@0 72 ngx.req.set_header("User-Id", jwt_obj.payload.sub)
paddy@0 73 ngx.req.set_header("Scopes", jwt_obj.payload.scope)
paddy@0 74 ngx.req.set_header("JWT-Scope", jwt_obj.payload.scope)
paddy@0 75 ngx.req.set_header("JWT-Issuer", jwt_obj.payload.iss)
paddy@0 76 ngx.req.set_header("JWT-Subject", jwt_obj.payload.sub)
paddy@0 77 ngx.req.set_header("JWT-Expiration-Time", jwt_obj.payload.exp)
paddy@0 78 ngx.req.set_header("JWT-Not-Before", jwt_obj.payload.nbf)
paddy@0 79 ngx.req.set_header("JWT-Issued-At", jwt_obj.payload.iat)
paddy@0 80 return
paddy@0 81 end
paddy@0 82 end
paddy@0 83 end
paddy@0 84 end
paddy@0 85
paddy@0 86 return M