Class: Verikloak::TokenDecoder

Inherits:
Object
  • Object
show all
Defined in:
lib/verikloak/token_decoder.rb

Overview

Verifies JWT tokens using a JWKs.

This class validates a JWT’s signature and standard claims (‘iss`, `aud`, `exp`, `nbf`, etc.) using the appropriate RSA public key selected by the JWT’s ‘kid` header. Only `RS256`-signed tokens with RSA JWKs are supported. It also supports a configurable clock skew (`leeway`) to account for minor time drift.

Examples:

decoder = Verikloak::TokenDecoder.new(
  jwks: jwks_keys,
  issuer: "https://keycloak.example.com/realms/myrealm",
  audience: "my-client-id",
  leeway: 30  # allow 30 seconds clock skew
)
payload = decoder.decode!(token)
puts payload["sub"]

Constant Summary collapse

DEFAULT_LEEWAY =

Default clock skew tolerance in seconds.

60

Instance Method Summary collapse

Constructor Details

#initialize(jwks:, issuer:, audience:, leeway: DEFAULT_LEEWAY, options: {}) ⇒ TokenDecoder

Initializes the decoder with a JWKs and verification criteria.

Parameters:

  • jwks (Array<Hash>)

    List of JWKs from the discovery document.

  • issuer (String)

    Expected ‘iss` value in the token.

  • audience (String)

    Expected ‘aud` value in the token.

  • leeway (Integer) (defaults to: DEFAULT_LEEWAY)

    Clock skew tolerance in seconds (optional).

  • options (Hash) (defaults to: {})

    Extra JWT verification options. Mirrors ruby-jwt options (e.g., :leeway, :verify_iat, :verify_expiration, :verify_not_before, :algorithms). NOTE: If both ‘leeway:` and `options` are provided, `options` takes precedence.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/verikloak/token_decoder.rb', line 36

def initialize(jwks:, issuer:, audience:, leeway: DEFAULT_LEEWAY, options: {})
  @jwks     = jwks
  @issuer   = issuer
  @audience = audience
  # Keep backward compatibility; can be overridden by options[:leeway]
  @leeway   = leeway
  # Normalize and store verification options
  @options  = symbolize_keys(options || {})
  @options_without_leeway = @options.except(:leeway).freeze

  # Build a kid-indexed hash for O(1) JWK lookup
  @jwk_by_kid = {}
  Array(@jwks).each do |j|
    kid_key = fetch_indifferent(j, 'kid')
    @jwk_by_kid[kid_key] = j if kid_key
  end
  @options.freeze
end

Instance Method Details

#decode!(token) ⇒ Hash

Decodes and verifies a JWT.

Parameters:

  • token (String)

    The JWT string to verify.

Returns:

  • (Hash)

    The decoded payload (claims).

Raises:

  • (TokenDecoderError)

    If verification fails. Possible error codes:

    • invalid_token

    • expired_token

    • not_yet_valid

    • invalid_issuer

    • invalid_audience

    • invalid_signature

    • unsupported_algorithm



67
68
69
70
71
72
73
74
75
76
77
# File 'lib/verikloak/token_decoder.rb', line 67

def decode!(token)
  with_error_handling do
    # Extract header without verifying signature (payload is ignored here).
    header = JWT.decode(token, nil, false).last
    validate_header(header)                       # check alg and kid present
    jwk        = find_key_by_kid(header)          # locate JWK by kid
    public_key = rsa_key_from_jwk(jwk)            # import RSA public key
    payload = decode_with_public_key(token, public_key) # verify signature & claims
    payload
  end
end