zaikio-jwt_auth 0.1.0 → 0.1.5

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: 6de8bcc2c7ef22d052c626bc463789dec9dba4c6d4e977ee543973736994440b
4
- data.tar.gz: c3c1b661b388fdef157831bccd203616913456f594762c4d0513967ebde8c468
3
+ metadata.gz: '0559a98a2221978a8a5c35e6968d667d9c17287b1421500ae0062bad169c0ece'
4
+ data.tar.gz: be9a95f5684dd8329f1b5446400ad8704f46422a364de01675538f3001c0bf59
5
5
  SHA512:
6
- metadata.gz: 694d2a013c3a8e41dc8b03a11b0817fadd8e636cd80809a4d66c04285b2ecfe1e2dcaaf919f7735556a001d6d6feb93c4541a1f61bce16689e117fbabe6bf55f
7
- data.tar.gz: 44c62f3c08102efb72e0ca8951562d02481bf5882742cd75ff4a27f722d80713f939c17cf05271cd870ef8b5fae3c00ad534b756aa981c32399e634a366b674d
6
+ metadata.gz: d4f1a51bed19e09d9ca09eb873377639f6f197f289742d74e17a6d6fba3e130608547530ca65daa6ab40c3faad4a00a8b695582fee084d78b8e96e897165301f
7
+ data.tar.gz: bf0eab6e6761239b33ae00d565ffc0b884fd70ddea3d589a91a0a1c7b2bf124e955048e8a5eebfbc7e42d1b37ed41fd96b0d6139b5f19ce5b60358bf69525612
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,26 +18,44 @@ 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 ||= options.merge(scopes: scopes)
32
53
  end
33
54
  end
34
55
 
35
56
  module InstanceMethods
36
57
  def authenticate_by_jwt
37
- unless jwt_from_auth_header
38
- render(status: :unauthorized, plain: "Please authenticate via Zaikio JWT") && return
39
- end
58
+ render_error("no_jwt_passed", status: :unauthorized) && return unless jwt_from_auth_header
40
59
 
41
60
  token_data = TokenData.new(jwt_payload)
42
61
 
@@ -48,30 +67,44 @@ module Zaikio
48
67
 
49
68
  send(:after_jwt_auth, token_data) if respond_to?(:after_jwt_auth)
50
69
  rescue JWT::ExpiredSignature
51
- render(status: :forbidden, plain: "JWT expired") && (return)
70
+ render_error("jwt_expired") && (return)
52
71
  rescue JWT::DecodeError
53
- render(status: :forbidden, plain: "Invalid JWT") && (return)
72
+ render_error("invalid_jwt") && (return)
73
+ end
74
+
75
+ def update_blacklisted_access_tokens_by_webhook
76
+ return unless params[:name] == "directory.revoked_access_token"
77
+
78
+ DirectoryCache.update("api/v1/blacklisted_access_tokens.json", expires_after: 60.minutes) do |data|
79
+ data["blacklisted_token_ids"] << params[:payload][:access_token_id]
80
+ data
81
+ end
82
+
83
+ render json: { received: true }
54
84
  end
55
85
 
56
86
  private
57
87
 
58
88
  def jwt_from_auth_header
89
+ return true if Zaikio::JWTAuth.mocked_jwt_payload
90
+
59
91
  auth_header = request.headers["Authorization"]
60
92
  auth_header.split("Bearer ").last if /Bearer/.match?(auth_header)
61
93
  end
62
94
 
63
95
  def jwt_payload
96
+ return Zaikio::JWTAuth.mocked_jwt_payload if Zaikio::JWTAuth.mocked_jwt_payload
97
+
64
98
  payload, = JWT.decode(jwt_from_auth_header, nil, true, algorithms: ["RS256"], jwks: JWK.loader)
65
99
 
66
100
  payload
67
101
  end
68
102
 
69
103
  def show_error_if_authorize_by_jwt_scopes_fails(token_data)
70
- if !self.class.authorize_by_jwt_scopes || token_data.scope?(self.class.authorize_by_jwt_scopes, action_name)
71
- return
72
- end
104
+ scope_data = self.class.authorize_by_jwt_scopes
105
+ return if !scope_data[:scopes] || token_data.scope?(scope_data[:scopes], action_name, scope_data[:app_name])
73
106
 
74
- render(status: :forbidden, plain: "Invalid scope")
107
+ render_error("unpermitted_scope")
75
108
  end
76
109
 
77
110
  def show_error_if_authorize_by_jwt_subject_type_fails(token_data)
@@ -80,21 +113,17 @@ module Zaikio
80
113
  return
81
114
  end
82
115
 
83
- render(status: :forbidden, plain: "Unallowed subject type")
116
+ render_error("unpermitted_subject")
84
117
  end
85
118
 
86
119
  def show_error_if_token_is_blacklisted(token_data)
87
- return unless blacklisted_token_ids.include?(token_data.jti)
120
+ return unless Zaikio::JWTAuth.revoked_jwt?(token_data.jti)
88
121
 
89
- render(status: :forbidden, plain: "Invalid token")
122
+ render_error("invalid_jwt")
90
123
  end
91
124
 
92
- def blacklisted_token_ids
93
- if Zaikio::JWTAuth.configuration.blacklisted_token_ids
94
- return Zaikio::JWTAuth.configuration.blacklisted_token_ids
95
- end
96
-
97
- DirectoryCache.fetch("api/v1/blacklisted_token_ids.json", expires_after: 5.minutes)["blacklisted_token_ids"]
125
+ def render_error(error, status: :forbidden)
126
+ render(status: status, json: { "errors" => [error] })
98
127
  end
99
128
  end
100
129
  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,12 @@ module Zaikio
25
33
  @payload["jti"]
26
34
  end
27
35
 
28
- def scope?(allowed_scopes, action_name)
36
+ def scope?(allowed_scopes, action_name, app_name = nil)
37
+ app_name ||= Zaikio::JWTAuth.configuration.app_name
29
38
  Array(allowed_scopes).map(&:to_s).any? do |allowed_scope|
30
39
  scope.any? do |s|
31
40
  parts = s.split(".")
32
- parts[0] == Zaikio::JWTAuth.configuration.app_name &&
41
+ parts[0] == app_name &&
33
42
  parts[1] == allowed_scope &&
34
43
  action_in_permission?(action_name, parts[2])
35
44
  end
@@ -1,5 +1,5 @@
1
1
  module Zaikio
2
2
  module JWTAuth
3
- VERSION = "0.1.0".freeze
3
+ VERSION = "0.1.5".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.0
4
+ version: 0.1.5
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-14 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/