Class: RackJwtVerifier::Verifier

Inherits:
Object
  • Object
show all
Defined in:
lib/rack_jwt_verifier/verifier.rb

Overview

Decodes and verifies JWTs against key material from a configured source: a static PEM, a PEM served at a URL, a JWKS endpoint, or a shared HMAC secret. Handles caching, key rotation, claim enforcement and optional jti replay protection so the middleware does not have to.

Constant Summary collapse

KeyFetchError =

Kept under the old constant so existing rescue Verifier::KeyFetchError code keeps working.

RackJwtVerifier::KeyFetchError
PUBLIC_KEY_CACHE_KEY =

Cache namespace for a fetched PEM; the full key also carries a digest of the URL.

KeySource::RemotePem::CACHE_KEY_PREFIX
JWKS_CACHE_KEY =

Cache namespace for a fetched JWKS; the full key also carries a digest of the URL.

KeySource::RemoteJwks::CACHE_KEY_PREFIX
CACHE_TTL_SECONDS =

The default TTL for cached key material (5 minutes)

KeySource::Remote::DEFAULT_CACHE_TTL
DEFAULT_HTTP_TIMEOUT =

Default open/read timeout (seconds) for fetching key material

KeySource::Remote::DEFAULT_HTTP_TIMEOUT
MAX_KEY_RESPONSE_BYTES =

Largest response body (bytes) accepted as key material

KeySource::Remote::MAX_RESPONSE_BYTES
DEFAULT_REFETCH_INTERVAL =

Minimum gap (seconds) between rotation-triggered refetches

KeySource::Remote::DEFAULT_REFETCH_INTERVAL
KEY_SOURCE_OPTIONS =

Exactly one of these tells the Verifier where its key comes from. Having shared_secret in the same exclusive set is what rules out "a secret next to a public key" — the classic RS256/HS256 confusion setup.

i[public_key public_key_url jwks_url shared_secret].freeze
HMAC_ALGORITHMS =

The HMAC family, only ever enabled by shared_secret.

%w[HS256 HS384 HS512].freeze
MIN_SECRET_BYTES =

RFC 7518 §3.2: an HMAC key must be at least as long as the hash output. Same numbers jwt_auth_client enforces on the signing side.

{ "HS256" => 32, "HS384" => 48, "HS512" => 64 }.freeze
DEFAULT_ASYMMETRIC_ALGORITHMS =

Default accepted algorithms per key-source family.

%w[RS256].freeze
DEFAULT_HMAC_ALGORITHMS =
%w[HS256].freeze
CLAIM_VERIFY_FLAGS =

ruby-jwt only validates an expected claim value (e.g. iss: "...") when the matching verify_* flag is also set. Map each claim to its flag so we can switch the flag on automatically whenever a value is supplied.

{
  iss: :verify_iss,
  aud: :verify_aud,
  sub: :verify_sub
}.freeze
DEFAULT_DECODE_OPTIONS =

Default options for JWT decoding to ensure strict security compliance. ruby-jwt only checks exp/nbf when the claim is present, so exp is also listed as required: a token with no expiry would otherwise be valid forever.

{
  verify_expiration: true,
  verify_not_before: true,
  required_claims: %w[exp].freeze,
  leeway: 60 # Allow a 60-second clock skew for "exp" and "nbf" claims
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Verifier

Returns a new instance of Verifier.

Parameters:

  • (defaults to: {})

    Configuration options.

Options Hash (options):

  • :public_key (String, OpenSSL::PKey)

    A static PEM public key or X.509 certificate.

  • :public_key_url (String)

    The https:// URL serving a PEM public key or certificate.

  • :jwks_url (String)

    The https:// URL serving a JSON Web Key Set.

  • :shared_secret (String, Hash)

    An HMAC secret (String) or { env: "VAR" }. Enables the HS* algorithms; mutually exclusive with the public-key sources.

  • :algorithms (Array<String>)

    Accepted signing algorithms (default: ["RS256"], or ["HS256"] with :shared_secret).

  • :allow_insecure_http (Boolean)

    Permit a plain http:// URL (development only).

  • :http_timeout (Numeric)

    Open/read/write timeout in seconds for the key fetch.

  • :cache_ttl (Integer)

    Seconds to cache fetched key material.

  • :refetch_interval (Numeric)

    Minimum seconds between rotation-triggered refetches.

  • :cache_store (Object)

    Optional custom cache object (must respond to #read and #write).

  • :replay_cache (Boolean, Object)

    true to track jti in :cache_store (or a fresh InProcessCache), or a cache store to track it in. Tokens are then required to carry a jti and a second presentation before exp is rejected.

  • :logger (Logger)

    Where cache-store failures are reported (default: silent).

  • :decode_options (Hash)

    Custom options for JWT.decode.

Raises:

  • for an unusable option set.



86
87
88
89
90
91
92
# File 'lib/rack_jwt_verifier/verifier.rb', line 86

def initialize(options = {})
  source_name = key_source_name(options)
  @key_source = build_key_source(source_name, options)
  @algorithms = build_algorithms(source_name, options)
  @decode_options = build_decode_options(options.fetch(:decode_options, {}), replay: replay_wanted?(options))
  @replay_guard = build_replay_guard(options, @decode_options[:leeway])
end

Instance Attribute Details

#algorithmsArray<String> (readonly)

The algorithms this verifier accepts, after defaults and policy.

Returns:



96
97
98
# File 'lib/rack_jwt_verifier/verifier.rb', line 96

def algorithms
  @algorithms
end

Instance Method Details

#verify(token) ⇒ Hash

Decodes and verifies the JWT.

Parameters:

  • The JWT string from the Authorization header.

Returns:

  • The decoded payload (the user claims).

Raises:

  • If the token is invalid, expired, replayed, or signature fails.

  • If the key material could not be obtained.

  • If replay protection is on and its store is unavailable.



104
105
106
107
108
109
110
# File 'lib/rack_jwt_verifier/verifier.rb', line 104

def verify(token)
  payload = decode_with_rotation(token)
  # Only a token that passed every other check is recorded: an expired or
  # mis-signed token must not be able to "burn" a jti.
  @replay_guard&.check!(payload)
  payload
end