zaikio-jwt_auth 0.1.1 → 0.1.6

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
  SHA256:
3
- metadata.gz: fc049533f5e0a82a2fa15f189514a997ae95b2a5cda50a8142d28d4776bacba3
4
- data.tar.gz: 9e0cd0c5aee054152abc83dfa717711ae0f3b4aedd53a1eae321091c3c2b0a9f
3
+ metadata.gz: d7b4d20c732061b0d41453bfa9f8b6b92bbeb6ff07dc146d17b0fd833356ca4a
4
+ data.tar.gz: c18a3da38ad85e0a04915ef11f865de78ea358ee8fa456d1244dde0bb3809d46
5
5
  SHA512:
6
- metadata.gz: dd1cf4ac348ab2fba0d05a94e3accf3db35f3170043bf51953810ef3d19538e9be1ecd2a130f921f22bd1b4e5351b051b5c92f9c7f47b28b5bbb4cc4f04f036a
7
- data.tar.gz: 9c0725178d406e5ce35c8af71b0c5f480739a65579b5c65d3fc8d0e79cba9714af14f64eb1e3e777f925f18d8314a8f61646dfac60d93eaf82d19c2cb5a9ac32
6
+ metadata.gz: df04a88a338e76b4ce746043e33ab172aa804d1e6b5079849325650ac0fe9f29f7c6d7b0aa9327e70f090b7e08c3acac21b9ad05da80356cf76c336c8f815d71
7
+ data.tar.gz: 58d19b43b1c68be4c983f3b03eaacac1d46ea3fdee6946b1151182ada650860c537761406db3352881103d5185b4827a835384de7288e4181174e856e1ca2da5
data/README.md CHANGED
@@ -6,7 +6,7 @@ Gem for JWT-Based authentication and authorization with zaikio.
6
6
 
7
7
  ## Installation
8
8
 
9
- Add this line to your application's Gemfile:
9
+ 1. Add this line to your application's Gemfile:
10
10
 
11
11
  ```ruby
12
12
  gem 'zaikio-jwt_auth'
@@ -22,7 +22,7 @@ Or install it yourself as:
22
22
  $ gem install zaikio-jwt_auth
23
23
  ```
24
24
 
25
- Configure the gem:
25
+ 2. Configure the gem:
26
26
 
27
27
  ```rb
28
28
  # config/initializers/zaikio_jwt_auth.rb
@@ -34,7 +34,7 @@ Zaikio::JWTAuth.configure do |config|
34
34
  end
35
35
  ```
36
36
 
37
- Extend your API application controller:
37
+ 3. Extend your API application controller:
38
38
 
39
39
  ```rb
40
40
  class API::ApplicationController < ActionController::Base
@@ -49,7 +49,42 @@ class API::ApplicationController < ActionController::Base
49
49
  end
50
50
  ```
51
51
 
52
- Add more restrictions to your resources:
52
+ 4. Update Revoked Access Tokens by Webhook
53
+
54
+ ```rb
55
+ # ENV['ZAIKIO_SHARED_SECRET'] needs to be defined first, you can find it on your
56
+ # app details page in zaikio. Fore more help read:
57
+ # https://docs.zaikio.com/guide/loom/receiving-events.html
58
+ class WebhooksController < ActionController::Base
59
+ include Zaikio::JWTAuth
60
+
61
+ before_action :verify_signature
62
+ before_action :update_blacklisted_access_tokens_by_webhook
63
+
64
+ def create
65
+ case params[:name]
66
+ # Manage other events
67
+ end
68
+
69
+ render json: { received: true }
70
+ end
71
+
72
+ private
73
+
74
+ def verify_signature
75
+ # Read More: https://docs.zaikio.com/guide/loom/receiving-events.html
76
+ unless ActiveSupport::SecurityUtils.secure_compare(
77
+ OpenSSL::HMAC.hexdigest("SHA256", "shared-secret", request.body.read),
78
+ request.headers["X-Loom-Signature"]
79
+ )
80
+ render json: { received: true }
81
+ end
82
+ end
83
+ end
84
+ ```
85
+
86
+
87
+ 5. Add more restrictions to your resources:
53
88
 
54
89
  ```rb
55
90
  class API::ResourcesController < API::ApplicationController
@@ -57,3 +92,21 @@ class API::ResourcesController < API::ApplicationController
57
92
  authorize_by_jwt_scopes 'resources'
58
93
  end
59
94
  ```
95
+
96
+ 6. Optionally, if you are using SSO: Check revoked tokens
97
+
98
+ Additionally, the API provides a method called `revoked_jwt?` which expects the `jti` of the JWT.
99
+
100
+ ```rb
101
+ Zaikio::JWTAuth.revoked_jwt?('jti-of-token') # returns true if token was revoked
102
+ ```
103
+
104
+ 7. Optionally, use the test helper module to mock JWTs in your minitests
105
+
106
+ ```rb
107
+ # in your test_helper.rb
108
+ include Zaikio::JWTAuth::TestHelper
109
+
110
+ # in your tests you can use:
111
+ mock_jwt(sub: 'Organization/123', scope: ['directory.organization.r'])
112
+ ```
@@ -5,6 +5,7 @@ require "zaikio/jwt_auth/configuration"
5
5
  require "zaikio/jwt_auth/directory_cache"
6
6
  require "zaikio/jwt_auth/jwk"
7
7
  require "zaikio/jwt_auth/token_data"
8
+ require "zaikio/jwt_auth/test_helper"
8
9
 
9
10
  module Zaikio
10
11
  module JWTAuth
@@ -17,18 +18,42 @@ module Zaikio
17
18
  yield(configuration)
18
19
  end
19
20
 
21
+ def self.revoked_jwt?(jti)
22
+ blacklisted_token_ids.include?(jti)
23
+ end
24
+
25
+ def self.blacklisted_token_ids
26
+ return [] if mocked_jwt_payload
27
+
28
+ return configuration.blacklisted_token_ids if configuration.blacklisted_token_ids
29
+
30
+ DirectoryCache.fetch("api/v1/blacklisted_access_tokens.json", expires_after: 60.minutes)["blacklisted_token_ids"]
31
+ end
32
+
20
33
  def self.included(base)
21
34
  base.send :include, InstanceMethods
22
35
  base.send :extend, ClassMethods
23
36
  end
24
37
 
38
+ def self.mocked_jwt_payload
39
+ @mocked_jwt_payload
40
+ end
41
+
42
+ def self.mocked_jwt_payload=(payload)
43
+ @mocked_jwt_payload = payload
44
+ end
45
+
25
46
  module ClassMethods
26
47
  def authorize_by_jwt_subject_type(type = nil)
27
48
  @authorize_by_jwt_subject_type ||= type
28
49
  end
29
50
 
30
- def authorize_by_jwt_scopes(scopes = nil)
31
- @authorize_by_jwt_scopes ||= scopes
51
+ def authorize_by_jwt_scopes(scopes = nil, options = {})
52
+ @authorize_by_jwt_scopes ||= []
53
+
54
+ @authorize_by_jwt_scopes << options.merge(scopes: scopes) if scopes
55
+
56
+ @authorize_by_jwt_scopes
32
57
  end
33
58
  end
34
59
 
@@ -51,23 +76,39 @@ module Zaikio
51
76
  render_error("invalid_jwt") && (return)
52
77
  end
53
78
 
79
+ def update_blacklisted_access_tokens_by_webhook
80
+ return unless params[:name] == "directory.revoked_access_token"
81
+
82
+ DirectoryCache.update("api/v1/blacklisted_access_tokens.json", expires_after: 60.minutes) do |data|
83
+ data["blacklisted_token_ids"] << params[:payload][:access_token_id]
84
+ data
85
+ end
86
+
87
+ render json: { received: true }
88
+ end
89
+
54
90
  private
55
91
 
56
92
  def jwt_from_auth_header
93
+ return true if Zaikio::JWTAuth.mocked_jwt_payload
94
+
57
95
  auth_header = request.headers["Authorization"]
58
96
  auth_header.split("Bearer ").last if /Bearer/.match?(auth_header)
59
97
  end
60
98
 
61
99
  def jwt_payload
100
+ return Zaikio::JWTAuth.mocked_jwt_payload if Zaikio::JWTAuth.mocked_jwt_payload
101
+
62
102
  payload, = JWT.decode(jwt_from_auth_header, nil, true, algorithms: ["RS256"], jwks: JWK.loader)
63
103
 
64
104
  payload
65
105
  end
66
106
 
67
107
  def show_error_if_authorize_by_jwt_scopes_fails(token_data)
68
- if !self.class.authorize_by_jwt_scopes || token_data.scope?(self.class.authorize_by_jwt_scopes, action_name)
69
- return
70
- end
108
+ return if token_data.scope_by_configurations?(
109
+ self.class.authorize_by_jwt_scopes,
110
+ action_name
111
+ )
71
112
 
72
113
  render_error("unpermitted_scope")
73
114
  end
@@ -82,19 +123,11 @@ module Zaikio
82
123
  end
83
124
 
84
125
  def show_error_if_token_is_blacklisted(token_data)
85
- return unless blacklisted_token_ids.include?(token_data.jti)
126
+ return unless Zaikio::JWTAuth.revoked_jwt?(token_data.jti)
86
127
 
87
128
  render_error("invalid_jwt")
88
129
  end
89
130
 
90
- def blacklisted_token_ids
91
- if Zaikio::JWTAuth.configuration.blacklisted_token_ids
92
- return Zaikio::JWTAuth.configuration.blacklisted_token_ids
93
- end
94
-
95
- DirectoryCache.fetch("api/v1/blacklisted_token_ids.json", expires_after: 5.minutes)["blacklisted_token_ids"]
96
- end
97
-
98
131
  def render_error(error, status: :forbidden)
99
132
  render(status: status, json: { "errors" => [error] })
100
133
  end
@@ -18,6 +18,19 @@ module Zaikio
18
18
  json["data"]
19
19
  end
20
20
 
21
+ def update(directory_path, options = {})
22
+ data = fetch(directory_path, options)
23
+ data = yield(data)
24
+ Zaikio::JWTAuth.configuration.redis.set("zaikio::jwt_auth::#{directory_path}", {
25
+ fetched_at: Time.now.to_i,
26
+ data: data
27
+ }.to_json)
28
+ end
29
+
30
+ def reset(directory_path)
31
+ Zaikio::JWTAuth.configuration.redis.del("zaikio::jwt_auth::#{directory_path}")
32
+ end
33
+
21
34
  private
22
35
 
23
36
  def cache_expired?(json, expires_after)
@@ -0,0 +1,23 @@
1
+ module Zaikio
2
+ module JWTAuth
3
+ module TestHelper
4
+ def after_setup
5
+ Zaikio::JWTAuth.mocked_jwt_payload = nil
6
+ super
7
+ end
8
+
9
+ def mock_jwt(extra_payload)
10
+ Zaikio::JWTAuth.mocked_jwt_payload = {
11
+ iss: "ZAI",
12
+ sub: nil,
13
+ aud: %w[test_app],
14
+ jti: "unique-access-token-id",
15
+ nbf: Time.now.to_i,
16
+ exp: 1.hour.from_now.to_i,
17
+ jku: "http://directory.zaikio.test/api/v1/jwt_public_keys.json",
18
+ scope: []
19
+ }.merge(extra_payload).stringify_keys
20
+ end
21
+ end
22
+ end
23
+ end
@@ -17,6 +17,14 @@ module Zaikio
17
17
  @payload = payload
18
18
  end
19
19
 
20
+ def audience
21
+ audiences.first
22
+ end
23
+
24
+ def audiences
25
+ @payload["aud"] || []
26
+ end
27
+
20
28
  def scope
21
29
  @payload["scope"]
22
30
  end
@@ -25,11 +33,30 @@ module Zaikio
25
33
  @payload["jti"]
26
34
  end
27
35
 
28
- def scope?(allowed_scopes, action_name)
36
+ # scope_options is an array of objects with:
37
+ # scope, app_name (optional), except/only (array, optional)
38
+ def scope_by_configurations?(scope_configurations, action_name)
39
+ configuration = scope_configurations.find do |scope_configuration|
40
+ if scope_configuration[:only]
41
+ Array(scope_configuration[:only]).any? { |a| a.to_s == action_name }
42
+ elsif scope_configuration[:except]
43
+ Array(scope_configuration[:except]).none? { |a| a.to_s == action_name }
44
+ else
45
+ true
46
+ end
47
+ end
48
+
49
+ return true unless configuration
50
+
51
+ scope?(configuration[:scopes], action_name, configuration[:app_name])
52
+ end
53
+
54
+ def scope?(allowed_scopes, action_name, app_name = nil)
55
+ app_name ||= Zaikio::JWTAuth.configuration.app_name
29
56
  Array(allowed_scopes).map(&:to_s).any? do |allowed_scope|
30
57
  scope.any? do |s|
31
58
  parts = s.split(".")
32
- parts[0] == Zaikio::JWTAuth.configuration.app_name &&
59
+ parts[0] == app_name &&
33
60
  parts[1] == allowed_scope &&
34
61
  action_in_permission?(action_name, parts[2])
35
62
  end
@@ -1,5 +1,5 @@
1
1
  module Zaikio
2
2
  module JWTAuth
3
- VERSION = "0.1.1".freeze
3
+ VERSION = "0.1.6".freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zaikio-jwt_auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Crispy Mountain GmbH
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-20 00:00:00.000000000 Z
11
+ date: 2020-02-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oj
@@ -68,6 +68,7 @@ files:
68
68
  - lib/zaikio/jwt_auth/directory_cache.rb
69
69
  - lib/zaikio/jwt_auth/jwk.rb
70
70
  - lib/zaikio/jwt_auth/railtie.rb
71
+ - lib/zaikio/jwt_auth/test_helper.rb
71
72
  - lib/zaikio/jwt_auth/token_data.rb
72
73
  - lib/zaikio/jwt_auth/version.rb
73
74
  homepage: https://www.zaikio.com/