class Rack::JsonWebTokenAuth
Rack
Middleware for JSON Web Token Authentication
Constants
- BEARER_TOKEN_REGEX
The last segment gets dropped for 'none' algorithm since there is no signature so both of these patterns are valid. All character chunks are base64url format and periods.
Bearer abc123.abc123.abc123 Bearer abc123.abc123.
- ENV_KEY
- PATH_INFO_HEADER_KEY
- VERSION
Public Class Methods
new(app, &block)
click to toggle source
# File lib/rack/json_web_token_auth.rb, line 21 def initialize(app, &block) @app = app # execute the block methods provided in the context of this class instance_eval(&block) end
Public Instance Methods
all_resources()
click to toggle source
# File lib/rack/json_web_token_auth.rb, line 86 def all_resources @all_resources ||= [] end
call(env)
click to toggle source
# File lib/rack/json_web_token_auth.rb, line 46 def call(env) resource = resource_for_path(env[PATH_INFO_HEADER_KEY]) # no matching `secured` or `unsecured` resource. # fail-safe with 401 unauthorized if resource.nil? raise TokenError, 'No resource for path defined. Deny by default.' end if resource.public_resource? # whitelisted as `unsecured`. skip all token authentication. @app.call(env) else # HTTP method not permitted if resource.invalid_http_method?(env['REQUEST_METHOD']) raise HttpMethodError, 'HTTP request method denied' end # Test that `env` has a well formed Authorization header unless Contract.valid?(env, RackRequestHttpAuth) raise TokenError, 'malformed Authorization header or token' end # Extract the token from the 'Authorization: Bearer token' string token = BEARER_TOKEN_REGEX.match(env['HTTP_AUTHORIZATION'])[1] # Verify the token and its claims are valid jwt_opts = resource.opts[:jwt] jwt = ::JwtClaims.verify(token, jwt_opts) handle_token(env, jwt) @app.call(env) end rescue TokenError => e return_401(e.message) rescue StandardError return_401 end
handle_token(env, jwt)
click to toggle source
# File lib/rack/json_web_token_auth.rb, line 112 def handle_token(env, jwt) if Contract.valid?(jwt, HashOf[ok: HashOf[Symbol => Any]]) # Authenticated! Pass all claims into the app env for app use # with the hash keys converted to strings to match Rack env. env[ENV_KEY] = Hashie.stringify_keys(jwt[:ok]) elsif Contract.valid?(jwt, HashOf[error: ArrayOf[Symbol]]) # a list of any registered claims that fail validation, if the JWT MAC is verified raise TokenError, "invalid JWT claims : #{jwt[:error].sort.join(', ')}" elsif Contract.valid?(jwt, HashOf[error: 'invalid JWT']) # the JWT MAC is not verified raise TokenError, 'invalid JWT' elsif Contract.valid?(jwt, HashOf[error: 'invalid input']) # otherwise raise TokenError, 'invalid JWT input' else raise TokenError, 'unhandled JWT error' end end
resource_for_path(path_info)
click to toggle source
# File lib/rack/json_web_token_auth.rb, line 91 def resource_for_path(path_info) all_resources.each do |r| found = r.resource_for_path(path_info) return found unless found.nil? end nil end
return_401(msg = nil)
click to toggle source
# File lib/rack/json_web_token_auth.rb, line 100 def return_401(msg = nil) body = msg.nil? ? 'Unauthorized' : "Unauthorized : #{msg}" headers = { 'WWW-Authenticate' => 'Bearer error="invalid_token"', 'Content-Type' => 'text/plain', 'Content-Length' => body.bytesize.to_s } [401, headers, [body]] end
secured(&block)
click to toggle source
# File lib/rack/json_web_token_auth.rb, line 28 def secured(&block) resources = Resources.new(public_resource: false) # execute the methods in the 'secured' block in the context of # a new Resources object resources.instance_eval(&block) all_resources << resources end
unsecured(&block)
click to toggle source
# File lib/rack/json_web_token_auth.rb, line 37 def unsecured(&block) resources = Resources.new(public_resource: true) # execute the methods in the 'unsecured' block in the context of # a new Resources object resources.instance_eval(&block) all_resources << resources end