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
     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