warden-jwt_auth 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1f24397de1422507d524eba4a8c60050e6a6abde
4
- data.tar.gz: b767bf87c6b29a12da5a64b4367f619b8685568e
3
+ metadata.gz: 48b37f5ba1a7935103634d140ab0c1fbbaea54f3
4
+ data.tar.gz: 853f242af0f389a92a4f20c66800ed5447708830
5
5
  SHA512:
6
- metadata.gz: 29f2e7ef4ea4de94a68b4cac0e394df30609fa706c6afb468d725000995a68c80227abd64392367b94fda770fe7884aac11041ecd742661d96eaa8045efb04c7
7
- data.tar.gz: ab4e91316b5ca085c1f2f1f267656097d788a2e1e5ad1cf9face21402f569aa989264c57bdb8310512d7028a0e10351697abf13ece06512c0470f9bf72b49f93
6
+ metadata.gz: d15c993a973fade506472bd525a886e2be2a2b5b03ca99223711254c3876739e94704b6f8bdd64a7c01832b5d8f96978768f18638b972be1db4027e2c24bde4d
7
+ data.tar.gz: ffc3fe32c2dd1cc8d4206d031bb57612615a61219f3daa136c3865dca8445bc437da056760299b0ce5988ce15e74155445936385fefce9e3a215a4ef0426c751
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
+ ## [0.3.0] - 2017-12-06
8
+ ### Added
9
+ - Add and call hook method `on_jwt_dispatch` on user instance
10
+ - Encode and validate an `aud` claim from the request headers
11
+
7
12
  ## [0.2.1] - 2017-12-04
8
13
  ### Added
9
14
  - Allow configuring classes as strings
data/README.md CHANGED
@@ -24,7 +24,7 @@ If what you need is a JWT authentication library for [devise](https://github.com
24
24
  ## Installation
25
25
 
26
26
  ```ruby
27
- gem 'warden-jwt_auth', '~> 0.2.1'
27
+ gem 'warden-jwt_auth', '~> 0.3.0'
28
28
  ```
29
29
 
30
30
  And then execute:
@@ -100,6 +100,13 @@ def jwt_payload
100
100
  end
101
101
  ```
102
102
 
103
+ Just when a token is going to be dispatched to a client, a hook method `on_jwt_dispatch` is invoked, only when it exist, on the user record. This method takes the `token` and the `payload` as arguments.
104
+
105
+ ```ruby
106
+ def on_jwt_dispatch(token, payload)
107
+ # Do something
108
+ end
109
+
103
110
  ### Middlewares addition
104
111
 
105
112
  You need to add `Warden::JWTAuth::Middleware` to your rack middlewares stack. Actually, it is just a wrapper which adds two middlewares that do the actual job: dispatching tokens and revoking tokens.
@@ -164,6 +171,12 @@ module RevocationStrategy
164
171
  end
165
172
  ```
166
173
 
174
+ ### Requesting client validation
175
+
176
+ Authentication will be refused if a client requesting to be authenticated through a token is not the same to which it was originally issued. To do so, the content of the header `JWT_AUD` (configurable via `config.aud_header`) is stored as `aud` claim. If you don't want to differentiate between clients, you don't need to provide that header.
177
+
178
+ **Important:** Be aware that this workflow is not bullet proof. In some scenarios a user can handcraft the request headers, therefore being able to impersonate any client. In such cases you could need something more robust, like an OAuth workflow with client id and client secret.
179
+
167
180
  ## Development
168
181
 
169
182
  There are docker and docker-compose files configured to create a development environment for this gem. So, if you use Docker you only need to run:
@@ -25,6 +25,12 @@ module Warden
25
25
  # Expiration time for tokens
26
26
  setting :expiration_time, 3600
27
27
 
28
+ # Request header which value will be encoded as `aud` claim in JWT. If
29
+ # the header is not present `aud` will be `nil`.
30
+ setting(:aud_header, 'JWT_AUD') do |value|
31
+ ('HTTP_' + value.upcase).tr('-', '_')
32
+ end
33
+
28
34
  # A hash of warden scopes as keys and user repositories as values. The
29
35
  # values can be either the constants themselves or the constant names.
30
36
  #
@@ -47,6 +47,14 @@ module Warden
47
47
  env['HTTP_AUTHORIZATION'] = value
48
48
  env
49
49
  end
50
+
51
+ # Returns header configured through `aud_header` option
52
+ #
53
+ # @param env [Hash] Rack env
54
+ # @return [String]
55
+ def self.aud_header(env)
56
+ env[JWTAuth.config.aud_header]
57
+ end
50
58
  end
51
59
  end
52
60
  end
@@ -16,6 +16,11 @@ module Warden
16
16
  # match the one encoded in the payload
17
17
  class WrongScope < JWT::DecodeError
18
18
  end
19
+
20
+ # Error raised when trying to decode a token which `aud` claim does not
21
+ # match with the expected one
22
+ class WrongAud < JWT::DecodeError
23
+ end
19
24
  end
20
25
  end
21
26
  end
@@ -4,7 +4,7 @@ module Warden
4
4
  module JWTAuth
5
5
  # Warden hooks
6
6
  class Hooks
7
- include JWTAuth::Import['mappings', 'dispatch_requests']
7
+ include JWTAuth::Import['mappings', 'dispatch_requests', 'aud_header']
8
8
 
9
9
  # `env` key where JWT is added
10
10
  PREPARED_TOKEN_ENV_KEY = 'warden-jwt_auth.token'
@@ -24,8 +24,7 @@ module Warden
24
24
  env = auth.env
25
25
  scope = opts[:scope]
26
26
  return unless token_should_be_added?(scope, env)
27
- token = UserEncoder.new.call(user, scope)
28
- env[PREPARED_TOKEN_ENV_KEY] = token
27
+ add_token_to_env(user, scope, env)
29
28
  end
30
29
 
31
30
  def token_should_be_added?(scope, env)
@@ -34,6 +33,14 @@ module Warden
34
33
  jwt_scope?(scope) && request_matches?(path_info, method)
35
34
  end
36
35
 
36
+ # :reek:ManualDispatch
37
+ # :reek:FeatureEnvy
38
+ def add_token_to_env(user, scope, env)
39
+ token, payload = UserEncoder.new.call(user, scope, env[aud_header])
40
+ user.on_jwt_dispatch(token, payload) if user.respond_to?(:on_jwt_dispatch)
41
+ env[PREPARED_TOKEN_ENV_KEY] = token
42
+ end
43
+
37
44
  def jwt_scope?(scope)
38
45
  jwt_scopes = mappings.keys
39
46
  jwt_scopes.include?(scope)
@@ -31,6 +31,14 @@ module Warden
31
31
  def jwt_payload
32
32
  {}
33
33
  end
34
+
35
+ # Does something just after a JWT for the user has been dispatched.
36
+ #
37
+ # @param _token [String]
38
+ # @param _payload [Hash]
39
+ def on_jwt_dispatch(_token, _payload)
40
+ raise NotImplementedError
41
+ end
34
42
  end
35
43
 
36
44
  # Strategy to manage JWT revocation
@@ -22,6 +22,13 @@ module Warden
22
22
  payload['scp'] == scope.to_s
23
23
  end
24
24
 
25
+ # Returns whether given aud matches with the one encoded in the payload
26
+ # @param payload [Hash] JWT payload
27
+ # @return [Boolean]
28
+ def self.aud_matches?(payload, aud)
29
+ payload['aud'] == aud
30
+ end
31
+
25
32
  # Returns the payload to encode for a given user in a scope
26
33
  #
27
34
  # @param user [Interfaces::User] an user, whatever it is
@@ -18,7 +18,8 @@ module Warden
18
18
  end
19
19
 
20
20
  def authenticate!
21
- user = UserDecoder.new.call(token, scope)
21
+ aud = EnvHelper.aud_header(env)
22
+ user = UserDecoder.new.call(token, scope, aud)
22
23
  success!(user)
23
24
  rescue JWT::DecodeError => exception
24
25
  fail!(exception.message)
@@ -21,15 +21,17 @@ module Warden
21
21
  #
22
22
  # @param token [String] a JWT
23
23
  # @param scope [Symbol] Warden scope
24
+ # @param aud [String] Expected aud claim
24
25
  # @return [Interfaces::User] an user, whatever it is
25
26
  # @raise [Errors::RevokedToken] when token has been revoked for the
26
27
  # encoded user
27
28
  # @raise [Errors::NilUser] when decoded user is nil
28
29
  # @raise [Errors::WrongScope] when encoded scope does not match with scope
30
+ # @raise [Errors::WrongAud] when encoded aud does not match with aud
29
31
  # argument
30
- def call(token, scope)
32
+ def call(token, scope, aud)
31
33
  payload = TokenDecoder.new.call(token)
32
- raise Errors::WrongScope, 'wrong scope' unless helper.scope_matches?(payload, scope)
34
+ check_valid_claims(payload, scope, aud)
33
35
  user = helper.find_user(payload)
34
36
  check_valid_user(payload, user, scope)
35
37
  user
@@ -37,14 +39,15 @@ module Warden
37
39
 
38
40
  private
39
41
 
40
- def revoked?(payload, user, scope)
41
- strategy = revocation_strategies[scope]
42
- strategy.jwt_revoked?(payload, user)
42
+ def check_valid_claims(payload, scope, aud)
43
+ raise Errors::WrongScope, 'wrong scope' unless helper.scope_matches?(payload, scope)
44
+ raise Errors::WrongAud, 'wrong aud' unless helper.aud_matches?(payload, aud)
43
45
  end
44
46
 
45
47
  def check_valid_user(payload, user, scope)
46
48
  raise Errors::NilUser, 'nil user' unless user
47
- raise Errors::RevokedToken, 'revoked token' if revoked?(payload, user, scope)
49
+ strategy = revocation_strategies[scope]
50
+ raise Errors::RevokedToken, 'revoked token' if strategy.jwt_revoked?(payload, user)
48
51
  end
49
52
  end
50
53
  end
@@ -12,17 +12,26 @@ module Warden
12
12
  @helper = PayloadUserHelper
13
13
  end
14
14
 
15
- # Encodes a user for given scope into a JWT. Payload generated includes a
16
- # `sub` claim which is build calling `jwt_subject` in `user`, and a custom
17
- # `scp` claim which value is `scope` as a string. The result of
18
- # calling `jwt_payload` in user is also merged into the payload.
15
+ # Encodes a user for given scope into a JWT.
16
+ #
17
+ # Payload generated includes:
18
+ #
19
+ # - a `sub` claim which is build calling `jwt_subject` in `user`
20
+ # - an `aud` claim taken as it is in the `aud` parameter
21
+ # - a custom `scp` claim taken as the value of the `scope` parameter
22
+ # as a string.
23
+ #
24
+ # The result of calling `jwt_payload` in user is also merged
25
+ # into the payload.
19
26
  #
20
27
  # @param user [Interfaces::User] an user, whatever it is
21
28
  # @param scope [Symbol] Warden scope
22
- # @return [String] encoded JWT
23
- def call(user, scope)
24
- payload = helper.payload_for_user(user, scope)
25
- TokenEncoder.new.call(payload)
29
+ # @param aud [String] JWT aud claim
30
+ # @return [String, String] encoded JWT and decoded payload
31
+ def call(user, scope, aud)
32
+ payload = helper.payload_for_user(user, scope).merge('aud' => aud)
33
+ token = TokenEncoder.new.call(payload)
34
+ [token, payload]
26
35
  end
27
36
  end
28
37
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Warden
4
4
  module JWTAuth
5
- VERSION = '0.2.1'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: warden-jwt_auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc Busqué
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-12-04 00:00:00.000000000 Z
11
+ date: 2017-12-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-configurable