Class: Rack::JsonWebTokenAuth

Inherits:
Object
  • Object
show all
Includes:
Contracts::Core
Defined in:
lib/rack/json_web_token_auth.rb,
lib/rack/json_web_token_auth/version.rb,
lib/rack/json_web_token_auth/resource.rb,
lib/rack/json_web_token_auth/resources.rb

Overview

Rack Middleware for JSON Web Token Authentication

Defined Under Namespace

Classes: Resource, Resources

Constant Summary collapse

C =
Contracts
ENV_KEY =
'jwt.claims'.freeze
PATH_INFO_HEADER_KEY =
'PATH_INFO'.freeze
VERSION =
'0.1.0'.freeze

Instance Method Summary collapse

Constructor Details

#initialize(app, &block) ⇒ JsonWebTokenAuth

Returns a new instance of JsonWebTokenAuth.



20
21
22
23
24
# File 'lib/rack/json_web_token_auth.rb', line 20

def initialize(app, &block)
  @app = app
  # execute the block methods provided in the context of this class
  instance_eval(&block)
end

Instance Method Details

#all_resourcesObject



103
104
105
# File 'lib/rack/json_web_token_auth.rb', line 103

def all_resources
  @all_resources ||= []
end

#call(env) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/rack/json_web_token_auth.rb', line 45

def call(env)
  begin
    resource = resource_for_path(env[PATH_INFO_HEADER_KEY])

    if resource.public_resource?
      # whitelisted as `unsecured`. skip all token authentication.
      @app.call(env)
    elsif resource.nil?
      # no matching `secured` or `unsecured` resource.
      # fail-safe with 401 unauthorized
      raise 'No resource for path defined. Deny by default.'
    else
      # a `secured` resource, validate the token to see if authenticated

      # Test that `env` has a well formed Authorization header
      unless Contract.valid?(env, C::RackRequestHttpAuth)
        raise 'malformed Authorization header or token'
      end

      # Extract the token from the 'Authorization: Bearer token' string
      token = C::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)

      # JwtClaims.verify returns a JWT claims set hash, if the
      # JWT Message Authentication Code (MAC), or signature,
      # are verified and the registered claims are also verified.
      if Contract.valid?(jwt, C::HashOf[ok: C::HashOf[Symbol => C::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, C::HashOf[error: C::ArrayOf[Symbol]])
        # a list of any registered claims that fail validation, if the JWT MAC is verified
        raise "invalid JWT claims : #{jwt[:error].sort.join(', ')}"
      elsif Contract.valid?(jwt, C::HashOf[error: 'invalid JWT'])
        # the JWT MAC is not verified
        raise 'invalid JWT'
      elsif Contract.valid?(jwt, C::HashOf[error: 'invalid input'])
        # otherwise
        raise 'invalid JWT input'
      else
        raise 'unhandled JWT error'
      end

      @app.call(env)
    end
  rescue StandardError => e
    body = e.message.nil? ? 'Unauthorized' : "Unauthorized : #{e.message}"
    headers = { 'WWW-Authenticate' => 'Bearer error="invalid_token"',
                'Content-Type' => 'text/plain',
                'Content-Length' => body.bytesize.to_s }
    [401, headers, [body]]
  end
end

#resource_for_path(path_info) ⇒ Object



108
109
110
111
112
113
114
115
# File 'lib/rack/json_web_token_auth.rb', line 108

def resource_for_path(path_info)
  all_resources.each do |r|
    if found = r.resource_for_path(path_info)
      return found
    end
  end
  nil
end

#secured(&block) ⇒ Object



27
28
29
30
31
32
33
# File 'lib/rack/json_web_token_auth.rb', line 27

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) ⇒ Object



36
37
38
39
40
41
42
# File 'lib/rack/json_web_token_auth.rb', line 36

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