Module: LcpRuby::Authentication::BearerJwtVerifier

Defined in:
lib/lcp_ruby/authentication/bearer_jwt_verifier.rb

Overview

Pure JWT validator for bearer authentication. Stateless; every call re-runs shape, signature, and claim invariants. Returns ‘[claims_hash, provider]` on success and `nil` otherwise — no exceptions surface to the caller.

Constant Summary collapse

CLOCK_SKEW =

seconds

60

Class Method Summary collapse

Class Method Details

.verify(token_string) ⇒ Array(Hash, Provider)?

Returns:



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/lcp_ruby/authentication/bearer_jwt_verifier.rb', line 16

def verify(token_string)
  return nil if token_string.nil? || token_string.to_s.empty?

  unverified = unverified_decode(token_string)
  return nil unless unverified

  header = unverified[:header]
  claims = unverified[:claims]

  return nil unless header["alg"] == "RS256"
  # RFC 7515 §4.1.11 — "crit" lists header params the verifier MUST
  # understand. We support none, so any non-empty crit array, or any
  # non-array crit value, forces reject.
  crit = header["crit"]
  return nil unless crit.nil? || (crit.is_a?(Array) && crit.empty?)

  kid = header["kid"].to_s
  return nil if kid.empty?

  provider = find_provider_by_issuer(claims["iss"])
  return nil unless provider
  return nil if provider.audience.to_s.empty?

  jwk = JwksCache.key_for(provider, kid: kid)
  return nil unless jwk

  verified = verify_signature(token_string, jwk)
  return nil unless verified

  verified_claims = verified.to_h.transform_keys(&:to_s)
  return nil unless claim_invariants_pass?(verified_claims, provider)

  [ verified_claims, provider ]
rescue StandardError => e
  # Last-resort guard — preserves "all errors → nil" contract while
  # surfacing the cause in logs (PII-scrubbed, no raw token).
  Rails.logger.info(
    "[lcp_ruby] BearerJwtVerifier rejected token: #{e.class}: #{e.message}"
  ) if defined?(Rails)
  nil
end