Class: Keycardai::OAuth::JWTVerifier

Inherits:
Object
  • Object
show all
Defined in:
lib/keycardai/oauth/jwt_verifier.rb

Overview

Verifies compact JWTs (RFC 7519) against the RFC 9068 access-token profile, fail-closed. Cheap policy checks (algorithm, trusted issuer, required claims, expiry, audience, kid) run before any key resolution, so a token carrying an attacker-controlled iss never drives key lookup or network I/O. Verification keys are resolved by (issuer, kid) through the injected keyring (see JWKSKeyring).

Every rejection raises InvalidTokenError; misconfiguration raises ConfigurationError at construction.

Constant Summary collapse

SUPPORTED_ALGORITHMS =
["RS256"].freeze
REQUIRED_CLAIMS =
%w[iss sub aud exp iat client_id].freeze

Instance Method Summary collapse

Constructor Details

#initialize(issuers:, keyring:, audiences: nil, algorithms: SUPPORTED_ALGORITHMS, clock_skew: 0, clock: -> { Time.now }) ⇒ JWTVerifier

Returns a new instance of JWTVerifier.

Parameters:

  • issuers (String, Array<String>)

    trusted-issuer allowlist

  • keyring (#key)

    resolves a verification key for (issuer, kid)

  • audiences (String, Array<String>, nil) (defaults to: nil)

    when set, the token's aud must intersect this set

  • algorithms (Array<String>) (defaults to: SUPPORTED_ALGORITHMS)

    allowed alg header values

  • clock_skew (Numeric) (defaults to: 0)

    leeway in seconds applied to exp and nbf

  • clock (#call) (defaults to: -> { Time.now })

    returns the current Time; override in tests

Raises:

  • (ConfigurationError)

    no trusted issuer, or an algorithm the verifier does not implement



30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/keycardai/oauth/jwt_verifier.rb', line 30

def initialize(issuers:, keyring:, audiences: nil, algorithms: SUPPORTED_ALGORITHMS,
               clock_skew: 0, clock: -> { Time.now })
  @issuers = Array(issuers).reject { |issuer| issuer.nil? || issuer.empty? }
  raise ConfigurationError, "JWTVerifier requires at least one trusted issuer" if @issuers.empty?

  unimplemented = Array(algorithms) - SUPPORTED_ALGORITHMS
  raise ConfigurationError, "unimplemented algorithms: #{unimplemented.join(", ")}" unless unimplemented.empty?

  @keyring = keyring
  @audiences = audiences.nil? ? nil : Array(audiences)
  @algorithms = Array(algorithms)
  @clock_skew = clock_skew
  @clock = clock
end

Instance Method Details

#verify(token) ⇒ Hash

Verify a compact JWT and return its claim set.

Parameters:

  • token (String)

Returns:

  • (Hash)

    the verified claims, string-keyed

Raises:



51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/keycardai/oauth/jwt_verifier.rb', line 51

def verify(token)
  header, claims, signature, signing_input = decode_parts(token)
  check_algorithm(header)
  check_issuer(claims)
  check_required_claims(claims)
  check_temporal(claims)
  check_audience(claims)
  kid = header["kid"]
  raise InvalidTokenError, "token header has no kid" if kid.nil? || kid.empty?

  key = @keyring.key(claims["iss"], kid)
  check_signature(key, signature, signing_input)
  claims
end