omniauth-authify 0.1.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.
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "json"
5
+ require "net/http"
6
+ require "uri"
7
+ require "jwt"
8
+ require "omniauth/authify/errors"
9
+
10
+ module OmniAuth
11
+ module Authify
12
+ # Validates OpenID Connect ID tokens issued by Authify.
13
+ #
14
+ # Verifies the RS256 signature against the signing keys published at the
15
+ # organization's JWKS endpoint, and the standard ID token claims:
16
+ # iss, sub, aud, exp, iat, auth_time (when max_age applies), and nonce.
17
+ class JwtValidator
18
+ # ID token signature algorithms this validator accepts
19
+ SUPPORTED_ALGORITHMS = %w[RS256].freeze
20
+ # Claims that must be present in every ID token
21
+ REQUIRED_CLAIMS = %w[iss sub aud exp iat].freeze
22
+ # Network failures that map to a {TokenValidationError} during JWKS fetches
23
+ NETWORK_ERRORS = [SocketError, Errno::ECONNREFUSED, Timeout::Error, Net::OpenTimeout,
24
+ OpenSSL::SSL::SSLError].freeze
25
+
26
+ # Creates a validator
27
+ #
28
+ # @param client_id [String] the OAuth2 client ID (expected `aud` value)
29
+ # @param issuer [String] the expected value of the ID token `iss` claim
30
+ # @param jwks_uri [String] the organization's JWKS endpoint URL
31
+ def initialize(client_id:, issuer:, jwks_uri:)
32
+ @client_id = client_id
33
+ @issuer = issuer
34
+ @jwks_uri = URI(jwks_uri)
35
+ @jwks = nil
36
+ end
37
+
38
+ # Decodes an ID token and verifies its signature and claims.
39
+ #
40
+ # @param jwt [String] the ID token
41
+ # @param authorize_params [Hash] per-login parameters stored at the
42
+ # start of the flow; may contain +nonce+, +leeway+, +max_age+ and
43
+ # +issuer+ (string or symbol keys)
44
+ # @return [Hash] the verified claims
45
+ # @raise [TokenValidationError] when the token cannot be verified
46
+ def verify(jwt, authorize_params = {})
47
+ raise TokenValidationError, "ID token is required but missing" if jwt.to_s.empty?
48
+
49
+ params = authorize_params || {}
50
+ claims, = decode(jwt)
51
+ verify_iss(claims, params)
52
+ verify_sub(claims)
53
+ verify_aud(claims)
54
+ verify_expiration(claims, params)
55
+ verify_iat(claims, params)
56
+ verify_auth_time(claims, params)
57
+ verify_nonce(claims, params)
58
+ claims
59
+ end
60
+
61
+ # Decodes an ID token, verifying its signature but skipping claim checks.
62
+ #
63
+ # @param jwt [String] the ID token
64
+ # @return [Array(Hash, Hash)] the claims and the JOSE header
65
+ # @raise [TokenValidationError] when the signature cannot be verified
66
+ def decode(jwt)
67
+ header = token_head(jwt)
68
+ algorithm = header["alg"]
69
+ unless SUPPORTED_ALGORITHMS.include?(algorithm)
70
+ raise TokenValidationError,
71
+ "Signature algorithm of #{algorithm.inspect} is not supported. " \
72
+ "Expected the ID token to be signed with RS256"
73
+ end
74
+
75
+ claims, = JWT.decode(
76
+ jwt,
77
+ nil,
78
+ true,
79
+ jwks: jwks_loader,
80
+ algorithms: [algorithm],
81
+ required_claims: REQUIRED_CLAIMS,
82
+ verify_iss: false,
83
+ verify_aud: false,
84
+ verify_expiration: false,
85
+ verify_iat: false
86
+ )
87
+ [claims, header]
88
+ rescue JWT::DecodeError, JWT::ExpiredSignature, JWT::JWKError => e
89
+ raise TokenValidationError, "ID token could not be verified: #{e.message}"
90
+ end
91
+
92
+ private
93
+
94
+ def verify_iss(claims, params)
95
+ expected = params[:issuer] || params["issuer"] || @issuer
96
+ return if claims["iss"] == expected
97
+
98
+ raise TokenValidationError,
99
+ "Issuer (iss) claim mismatch in the ID token, expected (#{expected}), " \
100
+ "found (#{claims["iss"].inspect})"
101
+ end
102
+
103
+ def verify_sub(claims)
104
+ subject = claims["sub"]
105
+ return if subject.is_a?(String) && !subject.empty?
106
+
107
+ raise TokenValidationError,
108
+ "Subject (sub) claim must be a string present in the ID token"
109
+ end
110
+
111
+ def verify_aud(claims)
112
+ audience = claims["aud"]
113
+ audiences = Array(audience)
114
+ if audience.to_s.empty?
115
+ raise TokenValidationError,
116
+ "Audience (aud) claim must be a string or array of strings present in the ID token"
117
+ end
118
+ return if audiences.include?(@client_id)
119
+
120
+ raise TokenValidationError,
121
+ "Audience (aud) claim mismatch in the ID token; expected (#{@client_id}), " \
122
+ "found (#{audiences.join(", ")})"
123
+ end
124
+
125
+ def verify_expiration(claims, params)
126
+ expiration = claims["exp"]
127
+ if !expiration.is_a?(Integer)
128
+ raise TokenValidationError,
129
+ "Expiration time (exp) claim must be a number present in the ID token"
130
+ elsif expiration <= Time.now.to_i - leeway(params)
131
+ raise TokenValidationError,
132
+ "Expiration time (exp) claim error in the ID token; " \
133
+ "current time (#{Time.now}) is after expiration time " \
134
+ "(#{Time.at(expiration + leeway(params))})"
135
+ end
136
+ end
137
+
138
+ def verify_iat(claims, params)
139
+ issued_at = claims["iat"]
140
+ if !issued_at.is_a?(Integer)
141
+ raise TokenValidationError,
142
+ "Issued At (iat) claim must be a number present in the ID token"
143
+ elsif issued_at > Time.now.to_i + leeway(params)
144
+ raise TokenValidationError,
145
+ "Issued At (iat) claim error in the ID token; issued in the future " \
146
+ "at #{Time.at(issued_at)} (current time #{Time.now})"
147
+ end
148
+ end
149
+
150
+ def verify_auth_time(claims, params)
151
+ max_age = params[:max_age] || params["max_age"]
152
+ return unless max_age
153
+
154
+ auth_time = claims["auth_time"]
155
+ if !auth_time.is_a?(Integer)
156
+ raise TokenValidationError,
157
+ "Authentication Time (auth_time) claim must be a number present in " \
158
+ "the ID token when Max Age (max_age) is specified"
159
+ elsif Time.now.to_i > auth_time + max_age.to_i + leeway(params)
160
+ raise TokenValidationError,
161
+ "Authentication Time (auth_time) claim in the ID token indicates that " \
162
+ "too much time has passed since the last end-user authentication"
163
+ end
164
+ end
165
+
166
+ def verify_nonce(claims, params)
167
+ nonce = params[:nonce] || params["nonce"]
168
+ return unless nonce
169
+
170
+ received = claims["nonce"]
171
+ if !received.is_a?(String) || received.empty?
172
+ raise TokenValidationError,
173
+ "Nonce (nonce) claim must be a string present in the ID token"
174
+ elsif received != nonce
175
+ raise TokenValidationError,
176
+ "Nonce (nonce) claim value mismatch in the ID token; " \
177
+ "expected (#{nonce}), found (#{received})"
178
+ end
179
+ end
180
+
181
+ def leeway(params)
182
+ (params[:leeway] || params["leeway"] || 60).to_i
183
+ end
184
+
185
+ def token_head(jwt)
186
+ parts = jwt.to_s.split(".")
187
+ raise TokenValidationError, "ID token could not be decoded" if parts.length != 3
188
+
189
+ JSON.parse(Base64.urlsafe_decode64(parts[0]))
190
+ rescue ArgumentError, JSON::ParserError => e
191
+ raise TokenValidationError, "ID token could not be decoded: #{e.message}"
192
+ end
193
+
194
+ # Returns a proc usable as JWT's +jwks+ option. Fetches the JWKS from the
195
+ # organization's endpoint (once) and refetches when the verification
196
+ # machinery reports a `kid` missing from the cached key set (rotated
197
+ # signing keys).
198
+ def jwks_loader
199
+ lambda do |options|
200
+ options ||= {}
201
+ refetch = options[:invalidate] || options[:kid_not_found]
202
+ { keys: fetch_keys(force: refetch) }
203
+ end
204
+ end
205
+
206
+ def fetch_keys(force: false)
207
+ @jwks = parse_jwks(fetch_jwks_response) if force || !@jwks
208
+ @jwks
209
+ end
210
+
211
+ def fetch_jwks_response
212
+ Net::HTTP.start(@jwks_uri.host, @jwks_uri.port,
213
+ use_ssl: @jwks_uri.scheme == "https") do |http|
214
+ request = Net::HTTP::Get.new(@jwks_uri.request_uri)
215
+ request["accept"] = "application/json"
216
+ http.request(request)
217
+ end
218
+ rescue *NETWORK_ERRORS => e
219
+ raise TokenValidationError,
220
+ "Could not fetch the Authify JWKS from #{@jwks_uri}: #{e.message}"
221
+ end
222
+
223
+ def parse_jwks(response)
224
+ unless response.is_a?(Net::HTTPSuccess)
225
+ raise TokenValidationError,
226
+ "Could not fetch the Authify JWKS from #{@jwks_uri}: HTTP #{response.code}"
227
+ end
228
+
229
+ parsed = JSON.parse(response.body, symbolize_names: true)
230
+ parsed[:keys] || []
231
+ rescue JSON::ParserError => e
232
+ raise TokenValidationError,
233
+ "Could not parse the Authify JWKS from #{@jwks_uri}: #{e.message}"
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OmniAuth
4
+ module Authify
5
+ VERSION = "0.1.1"
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Root namespace for the OmniAuth framework. Third-party strategies such as
4
+ # this gem attach under {OmniAuth::Strategies} and may carry helper classes
5
+ # in their own namespace (here: {OmniAuth::Authify}).
6
+ module OmniAuth
7
+ # OmniAuth strategy support for [Authify](https://github.com/authify/authify),
8
+ # a self-hosted, multi-tenant identity provider implementing OpenID Connect.
9
+ #
10
+ # See {OmniAuth::Strategies::Authify} for the strategy itself.
11
+ module Authify
12
+ autoload :ConfigurationError, "omniauth/authify/errors"
13
+ autoload :TokenValidationError, "omniauth/authify/errors"
14
+ autoload :JwtValidator, "omniauth/authify/jwt_validator"
15
+ end
16
+ end
17
+
18
+ require "omniauth/strategies/authify"
@@ -0,0 +1,287 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "omniauth-oauth2"
5
+ require "omniauth/authify/errors"
6
+ require "omniauth/authify/jwt_validator"
7
+
8
+ module OmniAuth
9
+ # Namespace for all OmniAuth strategies (core and third-party)
10
+ module Strategies
11
+ # OmniAuth strategy for Authify, a self-hosted, multi-tenant identity
12
+ # provider implementing OpenID Connect on top of OAuth 2.0.
13
+ #
14
+ # Since Authify is multi-tenant, both the server base URL and the
15
+ # organization slug are required; all endpoints (authorize, token,
16
+ # userinfo and JWKS) are scoped to the organization.
17
+ #
18
+ # @example Rails/Devise usage
19
+ # provider :authify,
20
+ # ENV["AUTHIFY_CLIENT_ID"],
21
+ # ENV["AUTHIFY_CLIENT_SECRET"],
22
+ # site: "https://authify.example.com",
23
+ # organization: "my-org"
24
+ #
25
+ # @example Sinatra usage
26
+ # use OmniAuth::Builder do
27
+ # provider :authify, ENV["AUTHIFY_CLIENT_ID"], ENV["AUTHIFY_CLIENT_SECRET"],
28
+ # site: "https://authify.example.com", organization: "my-org"
29
+ # end
30
+ class Authify < OmniAuth::Strategies::OAuth2
31
+ # Scopes requested when the user does not configure any
32
+ DEFAULT_SCOPE = "openid profile email"
33
+
34
+ option :name, "authify"
35
+
36
+ args %i[client_id client_secret site organization]
37
+
38
+ option :client_id, nil
39
+ option :client_secret, nil
40
+ option :site, nil
41
+ option :organization, nil
42
+
43
+ # Set to +false+ to skip ID token signature/claim verification and rely
44
+ # solely on the userinfo endpoint (not recommended).
45
+ option :verify_id_token, true
46
+
47
+ # Leeway (in seconds) allowed when validating time-based claims.
48
+ option :leeway, 60
49
+
50
+ # Set to +false+ to disable PKCE (S256). PKCE is enabled by default.
51
+ option :pkce, true
52
+
53
+ # Scopes to request from Authify; "openid" is required for an ID token.
54
+ option :scope, DEFAULT_SCOPE
55
+
56
+ option :client_options, {
57
+ site: nil,
58
+ authorize_url: nil,
59
+ token_url: nil
60
+ }
61
+
62
+ # Configure the underlying OAuth2 client URLs for the organization.
63
+ def client
64
+ validate_configuration!
65
+
66
+ base = org_base
67
+ options.client_options.site = base
68
+ options.client_options.authorize_url = "#{base}/oauth/authorize"
69
+ options.client_options.token_url = "#{base}/oauth/token"
70
+
71
+ super
72
+ end
73
+
74
+ uid { raw_info["sub"] }
75
+
76
+ info do
77
+ {
78
+ name: raw_info["name"],
79
+ email: raw_info["email"],
80
+ image: raw_info["picture"],
81
+ nickname: raw_info["preferred_username"],
82
+ first_name: raw_info["given_name"],
83
+ last_name: raw_info["family_name"],
84
+ location: raw_info["zoneinfo"],
85
+ phone: raw_info["phone_number"],
86
+ urls: raw_info["website"] ? { website: raw_info["website"] } : {}
87
+ }
88
+ end
89
+
90
+ credentials do
91
+ creds = {
92
+ "token" => access_token.token,
93
+ "expires" => access_token.expires?
94
+ }
95
+ creds["expires_at"] = access_token.expires_at if access_token.expires?
96
+ creds["refresh_token"] = access_token.refresh_token if access_token.refresh_token
97
+ creds["id_token"] = id_token if id_token
98
+
99
+ # Full ID token verification: signature (via JWKS), issuer, audience,
100
+ # times, and the per-login nonce. Raises TokenValidationError on
101
+ # failure, surfacing as an OmniAuth failure via callback_phase.
102
+ if options.verify_id_token
103
+ if id_token.nil?
104
+ raise ::OmniAuth::Authify::TokenValidationError,
105
+ "ID token is required but missing"
106
+ end
107
+
108
+ @verified_claims = jwt_validator.verify(id_token, stored_authorize_params)
109
+ end
110
+
111
+ creds
112
+ end
113
+
114
+ extra do
115
+ extras = { raw_info: raw_info }
116
+ extras[:id_info] = @verified_claims if @verified_claims
117
+ extras
118
+ end
119
+
120
+ # Builds authorize parameters; generates and stores a nonce for this
121
+ # login so the returned ID token can be bound to the request.
122
+ #
123
+ # An OIDC +prompt+ parameter on the request phase (e.g. "consent",
124
+ # "login", "none") is forwarded to Authify.
125
+ def authorize_params
126
+ params = super
127
+ params[:nonce] = SecureRandom.hex(16)
128
+ params[:leeway] = options.leeway if options.leeway
129
+ params[:prompt] = request.params["prompt"] if request.params.key?("prompt")
130
+
131
+ store_authorize_params(params)
132
+
133
+ params
134
+ end
135
+
136
+ # Initiates the authorization redirect after validating configuration.
137
+ #
138
+ # @return [Array] a Rack redirect response to Authify's authorize
139
+ # endpoint, or an OmniAuth failure when misconfigured
140
+ def request_phase
141
+ if missing_configuration?
142
+ fail!(:missing_configuration, CallbackError.new(
143
+ :missing_configuration,
144
+ "The :site and :organization options are required"
145
+ ))
146
+ else
147
+ super
148
+ end
149
+ end
150
+
151
+ # Completes the login: exchanges the code for tokens (via the
152
+ # OmniAuth::Strategies::OAuth2 machinery), verifies the ID token while
153
+ # building the credentials hash, and translates any validation failure
154
+ # into an OmniAuth `invalid_credentials` failure.
155
+ #
156
+ # The OAuth2 state check is performed here first because the parent
157
+ # class's `secure_compare` raises NoMethodError (on nil) rather than
158
+ # failing cleanly when the callback carries a state param but no state
159
+ # exists in the session (stale or replayed callbacks). The parent's own
160
+ # check is skipped for this invocation because ours is equivalent (the
161
+ # same constant-time comparison) minus the crash.
162
+ def callback_phase
163
+ return state_failure unless callback_state_matches?
164
+
165
+ begin
166
+ base_ignores_state = options.provider_ignores_state
167
+ options.provider_ignores_state = true
168
+ super
169
+ ensure
170
+ options.provider_ignores_state = base_ignores_state
171
+ end
172
+ rescue ::OmniAuth::Authify::TokenValidationError, ::OAuth2::Error, CallbackError => e
173
+ fail!(:invalid_credentials, e)
174
+ rescue Timeout::Error, Errno::ETIMEDOUT, ::OAuth2::TimeoutError,
175
+ ::OAuth2::ConnectionError => e
176
+ fail!(:timeout, e)
177
+ rescue SocketError => e
178
+ fail!(:failed_to_connect, e)
179
+ end
180
+
181
+ private
182
+
183
+ # Constant-time comparison of the callback's state parameter against
184
+ # the state stored in the session at the start of the flow. Returns
185
+ # +true+ for blank values when +provider_ignores_state+ is set.
186
+ def callback_state_matches?
187
+ return true if options.provider_ignores_state
188
+
189
+ callback_state = request.params["state"].to_s
190
+ session_state = session.delete("omniauth.state").to_s
191
+ return false if callback_state.empty? || session_state.empty?
192
+
193
+ constant_time_equal?(callback_state, session_state)
194
+ end
195
+
196
+ # Constant-time comparison of two strings; returns false for blank or
197
+ # differently-sized inputs.
198
+ def constant_time_equal?(string_a, string_b)
199
+ return false unless string_a.bytesize == string_b.bytesize
200
+
201
+ l = string_a.unpack("C#{string_a.bytesize}")
202
+
203
+ res = 0
204
+ string_b.each_byte { |byte| res |= byte ^ l.shift }
205
+ res.zero?
206
+ end
207
+
208
+ def state_failure
209
+ fail!(:csrf_detected, CallbackError.new(:csrf_detected, "CSRF detected"))
210
+ end
211
+
212
+ # Persists per-login parameters (nonce, leeway) in the session so they
213
+ # can be checked against the ID token at the callback.
214
+ def store_authorize_params(params)
215
+ stored = { nonce: params[:nonce] }
216
+ stored[:leeway] = params[:leeway] if params[:leeway]
217
+ stored[:max_age] = params[:max_age] if params[:max_age]
218
+ session["omniauth.authify.authorize_params"] = stored
219
+ end
220
+
221
+ def stored_authorize_params
222
+ @stored_authorize_params ||=
223
+ (session.delete("omniauth.authify.authorize_params") || {}).transform_keys(&:to_sym)
224
+ end
225
+
226
+ def missing_configuration?
227
+ [options.site, options.organization].any? { |value| value.nil? || value.to_s.strip.empty? }
228
+ end
229
+
230
+ def id_token
231
+ return @id_token if defined?(@id_token)
232
+
233
+ @id_token = normalized_id_token
234
+ end
235
+
236
+ def jwt_validator
237
+ @jwt_validator ||= ::OmniAuth::Authify::JwtValidator.new(
238
+ client_id: options.client_id,
239
+ issuer: normalized_base,
240
+ jwks_uri: "#{normalized_base}/.well-known/jwks"
241
+ )
242
+ end
243
+
244
+ def org_base
245
+ "#{options.site.to_s.strip.sub(%r{/+\z}, "")}/#{options.organization.to_s.strip}"
246
+ end
247
+
248
+ def validate_configuration!
249
+ return unless missing_configuration?
250
+
251
+ raise ::OmniAuth::Authify::ConfigurationError,
252
+ "Authify strategy requires both :site and :organization options"
253
+ end
254
+
255
+ def normalized_base
256
+ org_base
257
+ end
258
+
259
+ def normalized_id_token
260
+ value = access_token&.params&.[]("id_token") ||
261
+ access_token&.[]("id_token") ||
262
+ access_token&.[](:id_token)
263
+ value.to_s.empty? ? nil : value
264
+ end
265
+
266
+ # Identity claims for the auth hash. Prefers (signature-verified) ID
267
+ # token claims and falls back to the userinfo endpoint.
268
+ def raw_info
269
+ return @raw_info if @raw_info
270
+
271
+ @raw_info = if options.verify_id_token && id_token
272
+ jwt_validator.decode(id_token).first
273
+ else
274
+ access_token.get(userinfo_url, headers: {
275
+ "accept" => "application/json"
276
+ }).parsed || {}
277
+ end
278
+ end
279
+
280
+ def userinfo_url
281
+ "#{normalized_base}/oauth/userinfo"
282
+ end
283
+ end
284
+ end
285
+ end
286
+
287
+ OmniAuth.config.add_camelization "authify", "Authify"
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "omniauth/authify"
@@ -0,0 +1,15 @@
1
+ {
2
+ "packages": {
3
+ ".": {
4
+ "package-name": "omniauth-authify",
5
+ "changelog-path": "CHANGELOG.md",
6
+ "release-type": "ruby",
7
+ "bump-minor-pre-major": true,
8
+ "bump-patch-for-minor-pre-major": true,
9
+ "draft": false,
10
+ "prerelease": false,
11
+ "version-file": "lib/omniauth/authify/version.rb"
12
+ }
13
+ },
14
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
15
+ }
@@ -0,0 +1,86 @@
1
+ module OmniAuth
2
+ module Authify
3
+ # The gem's current version
4
+ VERSION: String
5
+
6
+ # Raised for configuration errors within the strategy itself
7
+ class ConfigurationError < StandardError
8
+ def initialize: (String message) -> void
9
+
10
+ def message: () -> String
11
+ end
12
+
13
+ # Raised when an Authify-issued ID token (or the signing keys used to
14
+ # verify it) cannot be validated
15
+ class TokenValidationError < StandardError
16
+ def initialize: (String message) -> void
17
+
18
+ def message: () -> String
19
+ end
20
+
21
+ # Validates OpenID Connect ID tokens issued by Authify
22
+ class JwtValidator
23
+ # ID token signature algorithms this validator accepts
24
+ SUPPORTED_ALGORITHMS: Array[String]
25
+ # Claims that must be present in every ID token
26
+ REQUIRED_CLAIMS: Array[String]
27
+ # Network failures that map to a TokenValidationError during JWKS fetches
28
+ NETWORK_ERRORS: Array[singleton(StandardError)]
29
+
30
+ # Creates a validator
31
+ #
32
+ # - client_id: the OAuth2 client ID (expected `aud` value)
33
+ # - issuer: the expected value of the ID token `iss` claim
34
+ # - jwks_uri: the organization's JWKS endpoint URL
35
+ def initialize: (client_id: String, issuer: String, jwks_uri: String) -> void
36
+
37
+ # Decodes an ID token and verifies its signature and claims
38
+ #
39
+ # - jwt: the ID token
40
+ # - authorize_params: per-login parameters stored at the start of the
41
+ # flow; may contain +nonce+, +leeway+, +max_age+ and +issuer+
42
+ # (string or symbol keys)
43
+ #
44
+ # Returns the verified claims
45
+ # Raises TokenValidationError when the token cannot be verified
46
+ def verify: (?Hash[Symbol | String, untyped] authorize_params) -> Hash[String, untyped]
47
+
48
+ # Decodes an ID token, verifying its signature but skipping claim
49
+ # checks
50
+ #
51
+ # - jwt: the ID token
52
+ #
53
+ # Returns a tuple of the claims and the JOSE header
54
+ # Raises TokenValidationError when the signature cannot be verified
55
+ def decode: () -> [Hash[String, untyped], Hash[String, untyped]]
56
+
57
+ private
58
+
59
+ def verify_iss: (Hash[String, untyped] claims, Hash[Symbol | String, untyped] params) -> void
60
+
61
+ def verify_sub: (Hash[String, untyped] claims) -> void
62
+
63
+ def verify_aud: (Hash[String, untyped] claims) -> void
64
+
65
+ def verify_expiration: (Hash[String, untyped] claims, Hash[Symbol | String, untyped] params) -> void
66
+
67
+ def verify_iat: (Hash[String, untyped] claims, Hash[Symbol | String, untyped] params) -> void
68
+
69
+ def verify_auth_time: (Hash[String, untyped] claims, Hash[Symbol | String, untyped] params) -> void
70
+
71
+ def verify_nonce: (Hash[String, untyped] claims, Hash[Symbol | String, untyped] params) -> void
72
+
73
+ def leeway: (Hash[Symbol | String, untyped] params) -> Integer
74
+
75
+ def token_head: (String? jwt) -> Hash[String, untyped]
76
+
77
+ def jwks_loader: () -> ^(?Hash[Symbol, untyped]) -> Hash[Symbol, Array[Hash[Symbol, untyped]]]
78
+
79
+ def fetch_keys: (?force: bool) -> Array[Hash[Symbol, untyped]]
80
+
81
+ def fetch_jwks_response: () -> (Net::HTTPSuccess | Net::HTTPResponse)
82
+
83
+ def parse_jwks: ((Net::HTTPSuccess | Net::HTTPResponse) response) -> Array[Hash[Symbol, untyped]]
84
+ end
85
+ end
86
+ end