clowk 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 724e8bb8826e85a7ca8a5ab0a35252a173ae351914318fdf1e7337b28d0acf24
4
- data.tar.gz: '0649d40341028b61fa6bc6c5ec48f11d4426db5a3919e231b043b512df5d8676'
3
+ metadata.gz: 5027a092ba122ab807f5c0340bc30399f7ab85f9ef41bc99c5610b68df6f47f7
4
+ data.tar.gz: 1f0dd44bf7696a1e3cffed45cb4c48edc92db3025007db39c6ea721f119619a8
5
5
  SHA512:
6
- metadata.gz: 9d641c9a88626dbadcd07e71aa433c34526c4e5de64e4727c593ceff50d227ca29ab8cee2352f0f69912e98b10dc4e341e81aa2382be49db192937a7b7fc7a3c
7
- data.tar.gz: 6d896475327369927e31eb9ea02c40d635739b945cd01389a2431d2717030364d99fff4eac03b6f10a084a00c80dc0381b5b3d9cf55ce9f5337960b364a19152
6
+ metadata.gz: 20a10b0715b1695694c51b915490ad852ba43004e9f1ec1b0fbc7af1d192737250234d4342ea1cac3db7ef6a94dc992b17795ee5ab2f0e0fcc6b23e7237a7c27
7
+ data.tar.gz: 124f14e7d6e53e7c904faa98befc85d18e7f1644f02d654a01c037555d589f4085069945435c7c81aaad36db4158b019b8f61faaeb359ac61839b09edf65d7a0
data/README.md CHANGED
@@ -69,7 +69,7 @@ end
69
69
  The callback flow includes:
70
70
 
71
71
  - session-backed `state` validation
72
- - JWT verification with your instance `secret_key`
72
+ - JWT verification against Clowk's published keys (RS256), falling back to your instance `secret_key` for legacy HS256 tokens
73
73
  - internal-only redirect sanitization
74
74
  - session reset before persisting the authenticated user
75
75
  - `httponly` cookie persistence with `SameSite=Lax`
@@ -103,13 +103,56 @@ Important settings:
103
103
 
104
104
  | Setting | Purpose |
105
105
  | --- | --- |
106
- | `secret_key` | Required. Used to verify JWT signatures. |
107
- | `publishable_key` | Preferred for auth URL resolution. The gem resolves the latest instance URL from it before sign in/sign up. |
106
+ | `secret_key` | Verifies legacy HS256 tokens. Not needed to verify RS256 tokens. |
107
+ | `publishable_key` | Preferred for auth URL resolution. The gem resolves the latest instance URL from it before sign in/sign up. Also the default `audience`. |
108
108
  | `subdomain_url` | Fallback auth domain when you do not want publishable-key-based resolution. |
109
+ | `jwks_url` | Where to fetch Clowk's public keys. Defaults to `<auth domain>/.well-known/jwks.json`. |
110
+ | `audience` | Expected `aud` claim on RS256 tokens. Defaults to `publishable_key`. Set to `nil` to skip the check. |
109
111
  | `prefix_by` | Prefix used to generate helper names. Default: `:clowk`. |
110
112
  | `mount_path` | Local mount prefix used by helper path generation. Default: `/clowk`. |
111
113
  | `callback_path` | Callback route Clowk redirects back to. Default: `/clowk/oauth/callback`. |
112
114
  | `http_logger` | Optional logger used by `Clowk::Http`. |
115
+ | `session_status_cache` | Where API-only apps cache session status. Defaults to `Rails.cache`. |
116
+
117
+ ### API-only Rails apps
118
+
119
+ `Clowk::Authenticable` works in `ActionController::API` controllers with no
120
+ extra setup — no session middleware, no cookie middleware:
121
+
122
+ ```ruby
123
+ class ApplicationController < ActionController::API
124
+ include Clowk::Authenticable
125
+
126
+ before_action :authenticate_clowk!
127
+ end
128
+ ```
129
+
130
+ What changes in that mode:
131
+
132
+ - **Nothing is written back.** A bearer request produces no `Set-Cookie` and no
133
+ session write. There is no browser to hold a cookie, a mobile client ignores
134
+ it, and writing a session per request defeats the point of being stateless.
135
+ - **Failures are always 401 JSON**, never a redirect — including when the
136
+ request carries no `Accept: application/json`. A 302 to a sign-in page is
137
+ useless to an API caller.
138
+ - **Session status is cached in `session_status_cache`** instead of the Rails
139
+ session, keyed by a digest of the token. Without it, `enforce_active_session`
140
+ would cost a round trip to Clowk on every authenticated request.
141
+
142
+ ### Token verification
143
+
144
+ Tokens signed with `RS256` are verified against Clowk's public key set, fetched
145
+ from `jwks_url` and cached in memory. The `kid` header selects the key, and a
146
+ `kid` the cache has not seen triggers a single refetch — that is what makes key
147
+ rotation invisible instead of an outage.
148
+
149
+ Because every consumer trusts the same public key under RS256, the `aud` claim
150
+ is what keeps a token minted for one app from verifying in another. It defaults
151
+ to your `publishable_key`, so the check is on without extra configuration.
152
+
153
+ Tokens signed with `HS256` still verify against `secret_key`, so tokens issued
154
+ before Clowk migrated to RS256 keep working until they expire. Audience is not
155
+ enforced on that path: those tokens predate the claim.
113
156
 
114
157
  Auth URL resolution priority:
115
158
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/concern"
4
+ require "digest"
4
5
 
5
6
  module Clowk
6
7
  module Authenticable
@@ -107,16 +108,30 @@ module Clowk
107
108
  end
108
109
 
109
110
  def clowk_sign_out!
110
- session.delete(Clowk.config.session_key)
111
- cookies.delete(Clowk.config.cookie_key)
111
+ clowk_session_store&.delete(Clowk.config.session_key)
112
+ clowk_cookie_jar&.delete(Clowk.config.cookie_key)
112
113
 
113
114
  @clowk_current_resource = nil
114
115
  end
115
116
 
116
117
  private
117
118
 
119
+ # ActionController::API is the reliable signal, and it has to be checked
120
+ # directly. Probing `session` does not work: with no session middleware
121
+ # loaded, `request.session` still returns a Session object that responds to
122
+ # `[]` and reads as nil — writes vanish, but nothing raises. Inferring
123
+ # "there is a session, so this is a browser" from that answered every API
124
+ # call with a 302 to a sign-in page the caller cannot follow.
125
+ def clowk_api_only?
126
+ defined?(ActionController::API) && is_a?(ActionController::API)
127
+ end
128
+
129
+ def clowk_api_request?
130
+ clowk_api_only? || clowk_session_store.nil? || request.format.json?
131
+ end
132
+
118
133
  def clowk_handle_unauthenticated
119
- if request.format.json?
134
+ if clowk_api_request?
120
135
  render json: {error: "Unauthorized"}, status: :unauthorized
121
136
  else
122
137
  redirect_to clowk_sign_in_path(return_to: request.fullpath)
@@ -124,13 +139,44 @@ module Clowk
124
139
  end
125
140
 
126
141
  def clowk_handle_expired_session(_session_info)
127
- if request.format.json?
142
+ if clowk_api_request?
128
143
  render json: {error: "Session expired or inactive"}, status: :unauthorized
129
144
  else
130
145
  redirect_to clowk_sign_in_path(return_to: request.fullpath)
131
146
  end
132
147
  end
133
148
 
149
+ # nil in API-only controllers. Not because `session` raises there — it does
150
+ # not, see clowk_api_only? — but because whatever it hands back writes to
151
+ # nowhere, and callers branch on this to decide whether persisting is worth
152
+ # doing at all.
153
+ def clowk_session_store
154
+ return @clowk_session_store if defined?(@clowk_session_store)
155
+
156
+ @clowk_session_store =
157
+ if clowk_api_only?
158
+ nil
159
+ else
160
+ begin
161
+ store = session
162
+ store.respond_to?(:[]) ? store : nil
163
+ rescue
164
+ nil
165
+ end
166
+ end
167
+ end
168
+
169
+ def clowk_cookie_jar
170
+ return @clowk_cookie_jar if defined?(@clowk_cookie_jar)
171
+
172
+ @clowk_cookie_jar = begin
173
+ jar = cookies
174
+ jar.respond_to?(:[]=) ? jar : nil
175
+ rescue
176
+ nil
177
+ end
178
+ end
179
+
134
180
  def verified_request_payload
135
181
  return unless extracted_token
136
182
 
@@ -152,7 +198,10 @@ module Clowk
152
198
  end
153
199
 
154
200
  def stored_session
155
- raw_session = session[Clowk.config.session_key]
201
+ store = clowk_session_store
202
+ return if store.nil?
203
+
204
+ raw_session = store[Clowk.config.session_key]
156
205
  return unless raw_session.respond_to?(:to_h)
157
206
 
158
207
  raw_session.to_h
@@ -163,25 +212,31 @@ module Clowk
163
212
  payload&.deep_symbolize_keys
164
213
  end
165
214
 
215
+ # No-op for API-only apps. A bearer request must not come back with a
216
+ # Set-Cookie: there is no browser to hold it, the mobile client ignores it,
217
+ # and writing a session per request defeats the point of being stateless.
166
218
  def persist_clowk_session(token, payload)
167
- session[Clowk.config.session_key] = {
219
+ store = clowk_session_store
220
+ return if store.nil?
221
+
222
+ store[Clowk.config.session_key] = {
168
223
  token:,
169
224
  user: payload,
170
225
  signed_in_at: Time.now.to_i
171
226
  }
172
227
 
173
- cookies[Clowk.config.cookie_key] = {
228
+ clowk_cookie_jar&.[]=(Clowk.config.cookie_key, {
174
229
  value: token,
175
230
  httponly: true,
176
231
  same_site: :lax,
177
232
  secure: request.ssl?
178
- }
233
+ })
179
234
  end
180
235
 
181
236
  def resolve_session_status
182
- cached = stored_session&.dig("session_status") || stored_session&.dig(:session_status)
237
+ cached = clowk_read_cached_session_status
183
238
 
184
- return cached&.deep_symbolize_keys if cached && clowk_session_status_fresh?
239
+ return cached if cached
185
240
 
186
241
  resource = clowk_current_resource
187
242
 
@@ -192,16 +247,56 @@ module Clowk
192
247
  result = client.tokens.verify_with_session(token: current_token)
193
248
  status = result&.dig(:session)
194
249
 
195
- if status && stored_session
196
- session[Clowk.config.session_key] = stored_session.merge(
250
+ clowk_write_cached_session_status(status) if status
251
+
252
+ status
253
+ rescue Clowk::InvalidTokenError
254
+ nil
255
+ end
256
+
257
+ def clowk_read_cached_session_status
258
+ if clowk_session_store
259
+ cached = stored_session&.dig("session_status") || stored_session&.dig(:session_status)
260
+
261
+ return cached&.deep_symbolize_keys if cached && clowk_session_status_fresh?
262
+
263
+ return nil
264
+ end
265
+
266
+ store = clowk_status_cache
267
+ return nil unless store
268
+
269
+ store.read(clowk_status_cache_key)&.deep_symbolize_keys
270
+ end
271
+
272
+ def clowk_write_cached_session_status(status)
273
+ if clowk_session_store
274
+ clowk_session_store[Clowk.config.session_key] = stored_session.merge(
197
275
  "session_status" => status,
198
276
  "session_status_checked_at" => Time.now.to_i
199
277
  )
278
+
279
+ return
200
280
  end
201
281
 
202
- status
203
- rescue Clowk::InvalidTokenError
204
- nil
282
+ ttl = Clowk.config.session_status_ttl.to_i
283
+ store = clowk_status_cache
284
+
285
+ return unless store && ttl.positive?
286
+
287
+ store.write(clowk_status_cache_key, status, expires_in: ttl)
288
+ end
289
+
290
+ # Stateless apps have no session to hang the cached status on, so without an
291
+ # external store every authenticated request would pay a round trip to
292
+ # Clowk. Keyed by token digest — the raw token must not end up in a cache
293
+ # key that could be logged.
294
+ def clowk_status_cache
295
+ Clowk.config.session_status_cache
296
+ end
297
+
298
+ def clowk_status_cache_key
299
+ "clowk:session_status:#{Digest::SHA256.hexdigest(current_token.to_s)}"
205
300
  end
206
301
 
207
302
  # Whether the cached status may still be trusted. Timestamped alongside the
@@ -12,7 +12,9 @@ module Clowk
12
12
  attr_accessor :http_retry_attempts
13
13
  attr_accessor :http_retry_interval
14
14
  attr_accessor :http_write_timeout
15
+ attr_writer :audience
15
16
  attr_accessor :issuer
17
+ attr_accessor :jwks_url
16
18
  attr_accessor :mount_path
17
19
  attr_accessor :publishable_key
18
20
  attr_accessor :prefix_by
@@ -23,6 +25,7 @@ module Clowk
23
25
  attr_accessor :enforce_active_session
24
26
  attr_accessor :on_session_expired
25
27
  attr_accessor :session_status_ttl
28
+ attr_writer :session_status_cache
26
29
 
27
30
  def initialize
28
31
  @api_base_url = "https://api.clowk.dev/api/v1"
@@ -51,6 +54,22 @@ module Clowk
51
54
  @session_status_ttl = 300
52
55
  end
53
56
 
57
+ # Where API-only apps cache session status, since they have no Rails session
58
+ # to hang it on. Defaults to Rails.cache when Rails is loaded; set to nil to
59
+ # check with Clowk on every authenticated request.
60
+ def session_status_cache
61
+ return @session_status_cache if defined?(@session_status_cache)
62
+
63
+ @session_status_cache = (defined?(::Rails) && ::Rails.respond_to?(:cache)) ? ::Rails.cache : nil
64
+ end
65
+
66
+ # Defaults to the publishable key, which is what Clowk stamps into `aud`.
67
+ # Consumers already configure that key, so audience checking is on by
68
+ # default rather than something you have to know to switch on.
69
+ def audience
70
+ @audience.nil? ? @publishable_key : @audience
71
+ end
72
+
54
73
  def after_sign_in_path
55
74
  resolve_path(@after_sign_in_path)
56
75
  end
data/lib/clowk/engine.rb CHANGED
@@ -9,6 +9,13 @@ module Clowk
9
9
  include Clowk::Helpers::UrlHelpers
10
10
  end
11
11
 
12
+ # API-only apps never load :action_controller_base, so without this hook
13
+ # clowk_sign_in_path — reached from the unauthenticated fallback — raises
14
+ # NoMethodError instead of rendering a 401.
15
+ ActiveSupport.on_load(:action_controller_api) do
16
+ include Clowk::Helpers::UrlHelpers
17
+ end
18
+
12
19
  ActiveSupport.on_load(:action_view) do
13
20
  include Clowk::Helpers::UrlHelpers
14
21
  end
data/lib/clowk/jwks.rb ADDED
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "json"
5
+ require "openssl"
6
+ require "uri"
7
+
8
+ module Clowk
9
+ # Fetches and caches Clowk's public key set.
10
+ #
11
+ # Keys are cached process-wide because verifying a token must not cost an
12
+ # HTTP round trip. A `kid` the cache has never seen forces one refetch — that
13
+ # is what makes key rotation invisible to consumers instead of an outage.
14
+ class Jwks
15
+ CACHE_TTL = 600
16
+ WELL_KNOWN_PATH = "/.well-known/jwks.json"
17
+
18
+ # Rotation should resolve in one refetch. Anything beyond that is a
19
+ # misconfigured issuer or a forged `kid`, and hammering the auth server on
20
+ # every unverifiable token turns a bad token into an outage.
21
+ REFETCH_COOLDOWN = 10
22
+
23
+ @cache_mutex = Mutex.new
24
+
25
+ class << self
26
+ def clear_cache!
27
+ @cache_mutex.synchronize do
28
+ @cache = {}
29
+ @last_refetch_at = {}
30
+ end
31
+ end
32
+
33
+ def key_for(kid, jwks_url: Clowk.config.jwks_url)
34
+ url = jwks_url || default_url
35
+
36
+ key = lookup(url, kid)
37
+ return key if key
38
+
39
+ # Cold or expired cache: this is the ordinary first fetch, not a
40
+ # rotation, so it must not spend the miss budget. Data is fresh
41
+ # afterwards, so a miss here means the kid is genuinely unknown.
42
+ return fetch_and_lookup(url, kid) unless warm?(url)
43
+
44
+ # Warm cache and an unknown kid: the set may have rotated under us.
45
+ return nil unless claim_refetch_slot!(url)
46
+
47
+ fetch_and_lookup(url, kid)
48
+ end
49
+
50
+ def default_url
51
+ base = Clowk.config.subdomain_url || Clowk::Subdomain.resolve_url!
52
+
53
+ "#{base.to_s.chomp("/")}#{WELL_KNOWN_PATH}"
54
+ rescue ConfigurationError
55
+ raise ConfigurationError, "set jwks_url, subdomain_url or publishable_key to verify RS256 tokens"
56
+ end
57
+
58
+ private
59
+
60
+ def fetch_and_lookup(url, kid)
61
+ fetch!(url)
62
+ lookup(url, kid)
63
+ end
64
+
65
+ def warm?(url)
66
+ @cache_mutex.synchronize do
67
+ entry = cache[url]
68
+
69
+ !entry.nil? && entry[:expires_at] > Time.now
70
+ end
71
+ end
72
+
73
+ def lookup(url, kid)
74
+ @cache_mutex.synchronize do
75
+ entry = cache[url]
76
+
77
+ next nil unless entry
78
+ next nil if entry[:expires_at] <= Time.now
79
+
80
+ entry[:keys][kid]
81
+ end
82
+ end
83
+
84
+ # Throttles only the refetches a cache miss triggers, not ordinary TTL
85
+ # refreshes. A rotation is picked up on the very next token; a storm of
86
+ # forged kids costs at most one fetch per cooldown window.
87
+ def claim_refetch_slot!(url)
88
+ @cache_mutex.synchronize do
89
+ last = last_refetch_at[url]
90
+
91
+ next false if last && Time.now - last < REFETCH_COOLDOWN
92
+
93
+ last_refetch_at[url] = Time.now
94
+ true
95
+ end
96
+ end
97
+
98
+ def fetch!(url)
99
+ uri = URI.parse(url)
100
+ base = uri.dup
101
+ base.path = ""
102
+ base.query = nil
103
+ base.fragment = nil
104
+
105
+ response = Http.get(base_url: base.to_s, path: uri.path, logger: Clowk.config.http_logger)
106
+
107
+ raise InvalidTokenError, "could not fetch JWKS from #{url}" unless response.success?
108
+
109
+ keys = parse(response.body)
110
+
111
+ @cache_mutex.synchronize do
112
+ cache[url] = {keys: keys, expires_at: Time.now + CACHE_TTL}
113
+ end
114
+
115
+ keys
116
+ end
117
+
118
+ def parse(body)
119
+ payload = body.is_a?(Hash) ? body : JSON.parse(body.to_s)
120
+ entries = payload["keys"] || payload[:keys] || []
121
+
122
+ entries.each_with_object({}) do |jwk, acc|
123
+ jwk = jwk.transform_keys(&:to_s)
124
+ next unless jwk["kty"] == "RSA" && jwk["kid"]
125
+
126
+ acc[jwk["kid"]] = to_rsa(jwk)
127
+ end
128
+ rescue JSON::ParserError => e
129
+ raise InvalidTokenError, "malformed JWKS document: #{e.message}"
130
+ end
131
+
132
+ # Built through DER rather than assigning n/e onto a blank key: OpenSSL 3
133
+ # made key objects immutable, so `set_key` no longer exists.
134
+ def to_rsa(jwk)
135
+ sequence = OpenSSL::ASN1::Sequence([
136
+ OpenSSL::ASN1::Integer(decode_bn(jwk["n"])),
137
+ OpenSSL::ASN1::Integer(decode_bn(jwk["e"]))
138
+ ])
139
+
140
+ OpenSSL::PKey::RSA.new(sequence.to_der)
141
+ end
142
+
143
+ def decode_bn(value)
144
+ OpenSSL::BN.new(Base64.urlsafe_decode64(pad(value.to_s)), 2)
145
+ end
146
+
147
+ # JWKS values are unpadded base64url; Ruby's decoder wants padding.
148
+ def pad(value)
149
+ remainder = value.length % 4
150
+
151
+ remainder.zero? ? value : value + ("=" * (4 - remainder))
152
+ end
153
+
154
+ def cache
155
+ @cache ||= {}
156
+ end
157
+
158
+ def last_refetch_at
159
+ @last_refetch_at ||= {}
160
+ end
161
+ end
162
+ end
163
+ end
@@ -4,24 +4,87 @@ require "jwt"
4
4
 
5
5
  module Clowk
6
6
  class JwtVerifier
7
- ALGORITHM = "HS256"
7
+ LEGACY_ALGORITHM = "HS256"
8
+ ASYMMETRIC_ALGORITHM = "RS256"
8
9
 
9
- def initialize(secret_key: Clowk.config.secret_key, issuer: Clowk.config.issuer)
10
+ # Kept because it is part of the gem's public surface. Prefer
11
+ # LEGACY_ALGORITHM — this no longer describes what the verifier accepts.
12
+ ALGORITHM = LEGACY_ALGORITHM
13
+
14
+ def initialize(secret_key: Clowk.config.secret_key, issuer: Clowk.config.issuer,
15
+ audience: Clowk.config.audience, jwks_url: Clowk.config.jwks_url)
10
16
  @secret_key = secret_key
11
17
  @issuer = issuer
18
+ @audience = audience
19
+ @jwks_url = jwks_url
12
20
  end
13
21
 
14
22
  def verify(token)
23
+ payload, = asymmetric?(token) ? verify_asymmetric(token) : verify_legacy(token)
24
+ payload.deep_symbolize_keys
25
+ rescue JWT::DecodeError, JWT::VerificationError, JWT::ExpiredSignature,
26
+ JWT::InvalidIssuerError, JWT::InvalidAudError => e
27
+ raise InvalidTokenError, e.message
28
+ end
29
+
30
+ private
31
+
32
+ def asymmetric?(token)
33
+ header(token)["alg"] == ASYMMETRIC_ALGORITHM
34
+ rescue JWT::DecodeError
35
+ false
36
+ end
37
+
38
+ def header(token)
39
+ JWT.decode(token, nil, false).last
40
+ end
41
+
42
+ # Tokens signed with the shared instance secret. Kept so tokens issued
43
+ # before the RS256 migration keep working until they expire.
44
+ def verify_legacy(token)
15
45
  raise ConfigurationError, "missing Clowk secret_key" if @secret_key.to_s.empty?
16
46
 
17
- options = {algorithm: ALGORITHM}
18
- options[:iss] = @issuer if @issuer
19
- options[:verify_iss] = @issuer.present?
47
+ JWT.decode(token, @secret_key, true, base_options.merge(algorithm: LEGACY_ALGORITHM))
48
+ end
20
49
 
21
- payload, = JWT.decode(token, @secret_key, true, options)
22
- payload.deep_symbolize_keys
23
- rescue JWT::DecodeError, JWT::VerificationError, JWT::ExpiredSignature, JWT::InvalidIssuerError => e
24
- raise InvalidTokenError, e.message
50
+ def verify_asymmetric(token)
51
+ kid = header(token)["kid"]
52
+
53
+ raise InvalidTokenError, "token is missing kid header" if kid.to_s.empty?
54
+
55
+ key = Jwks.key_for(kid, jwks_url: @jwks_url)
56
+
57
+ raise InvalidTokenError, "unknown signing key: #{kid}" unless key
58
+
59
+ JWT.decode(token, key, true, asymmetric_options)
60
+ end
61
+
62
+ def base_options
63
+ opts = {}
64
+
65
+ if @issuer
66
+ opts[:iss] = @issuer
67
+ opts[:verify_iss] = true
68
+ end
69
+
70
+ opts
71
+ end
72
+
73
+ # Audience is enforced only here, and deliberately so. Under RS256 every
74
+ # consumer trusts the same public key, so without `aud` a token minted for
75
+ # another app would verify cleanly — the shared secret used to prevent that
76
+ # by accident. HS256 tokens are already scoped by the per-instance secret,
77
+ # and the ones issued before this claim existed carry no `aud` at all, so
78
+ # enforcing it there would reject valid tokens during the migration window.
79
+ def asymmetric_options
80
+ opts = base_options.merge(algorithm: ASYMMETRIC_ALGORITHM)
81
+
82
+ if @audience
83
+ opts[:aud] = @audience
84
+ opts[:verify_aud] = true
85
+ end
86
+
87
+ opts
25
88
  end
26
89
  end
27
90
  end
data/lib/clowk/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clowk
4
- VERSION = "0.4.1"
4
+ VERSION = "0.5.1"
5
5
  end
data/lib/clowk.rb CHANGED
@@ -48,6 +48,7 @@ require_relative "clowk/sdk/token"
48
48
  require_relative "clowk/sdk/session_config"
49
49
  require_relative "clowk/sdk/client"
50
50
  require_relative "clowk/subdomain"
51
+ require_relative "clowk/jwks"
51
52
  require_relative "clowk/jwt_verifier"
52
53
  require_relative "clowk/helpers/url_helpers"
53
54
  require_relative "clowk/middleware/token_extractor"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clowk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Clowk
@@ -115,6 +115,7 @@ files:
115
115
  - lib/clowk/http/response.rb
116
116
  - lib/clowk/http/retry_middleware.rb
117
117
  - lib/clowk/http/timeout_middleware.rb
118
+ - lib/clowk/jwks.rb
118
119
  - lib/clowk/jwt_verifier.rb
119
120
  - lib/clowk/middleware/token_extractor.rb
120
121
  - lib/clowk/sdk/client.rb