Class: Safire::JWTAssertion

Inherits:
Object
  • Object
show all
Defined in:
lib/safire/jwt_assertion.rb

Overview

Generates JWT client assertions for SMART on FHIR confidential asymmetric authentication.

This class creates signed JWTs according to the SMART App Launch STU 2.2.0 specification for private_key_jwt client authentication.

Examples:

Creating a JWT assertion with RSA key

assertion = Safire::JWTAssertion.new(
  client_id: 'my_app',
  token_endpoint: 'https://auth.example.com/token',
  private_key: OpenSSL::PKey::RSA.new(File.read('private.pem')),
  kid: 'key-id-123'
)
jwt = assertion.to_jwt  # => signed JWT string

With explicit algorithm and jku header

assertion = Safire::JWTAssertion.new(
  client_id: 'my_app',
  token_endpoint: 'https://auth.example.com/token',
  private_key: private_key,
  kid: 'key-id-123',
  algorithm: 'RS384',
  jku: 'https://app.example.com/.well-known/jwks.json'
)

See Also:

Constant Summary collapse

MAX_EXPIRATION_SECONDS =

Maximum expiration time allowed per SMART specification (5 minutes)

300
DEFAULT_EXPIRATION_SECONDS =

Default expiration time (5 minutes)

300
SUPPORTED_ALGORITHMS =

Supported signing algorithms (required by SMART specification)

%w[RS384 ES384].freeze
REQUIRED_PARAMS =

Required parameters for JWT assertion

%i[client_id token_endpoint kid].freeze
SUPPORTED_EC_CURVES =

EC curve names that support ES384 algorithm

%w[secp384r1 P-384].freeze
DEFAULT_RSA_ALGORITHM =

Default algorithm for RSA keys (required by SMART spec)

'RS384'.freeze
DEFAULT_EC_ALGORITHM =

Default algorithm for EC keys (required by SMART spec)

'ES384'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client_id:, token_endpoint:, private_key:, kid:, algorithm: nil, jku: nil, expiration_seconds: DEFAULT_EXPIRATION_SECONDS) ⇒ JWTAssertion

Creates a new JWT assertion generator.

Parameters:

  • client_id (String)

    the client_id to use as iss and sub claims

  • token_endpoint (String)

    the token endpoint URL to use as aud claim

  • private_key (OpenSSL::PKey::RSA, OpenSSL::PKey::EC, String)

    the private key for signing (can be a PEM-encoded string)

  • kid (String)

    the key ID matching the registered public key

  • algorithm (String, nil) (defaults to: nil)

    the signing algorithm (auto-detected from key type if nil)

  • jku (String, nil) (defaults to: nil)

    optional JWKS URL for jku header (must be HTTPS)

  • expiration_seconds (Integer) (defaults to: DEFAULT_EXPIRATION_SECONDS)

    expiration time in seconds (default: 300, max: 300)

Raises:

  • (ArgumentError)

    if required parameters are missing or invalid



82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/safire/jwt_assertion.rb', line 82

def initialize(client_id:, token_endpoint:, private_key:, kid:, algorithm: nil, jku: nil,
               expiration_seconds: DEFAULT_EXPIRATION_SECONDS)
  @client_id = client_id
  @token_endpoint = token_endpoint
  @private_key = parse_private_key(private_key)
  @kid = kid
  @algorithm = algorithm || detect_algorithm(@private_key)
  @jku = jku
  @expiration_seconds = [expiration_seconds, MAX_EXPIRATION_SECONDS].min

  validate!
end

Instance Attribute Details

#algorithmString (readonly)

Returns the signing algorithm (RS384 or ES384).

Returns:

  • (String)

    the signing algorithm (RS384 or ES384)



68
# File 'lib/safire/jwt_assertion.rb', line 68

attr_reader :client_id, :token_endpoint, :private_key, :kid, :algorithm, :jku, :expiration_seconds

#client_idString (readonly)

Returns the client_id used as iss and sub claims in the JWT.

Returns:

  • (String)

    the client_id used as iss and sub claims in the JWT



68
69
70
# File 'lib/safire/jwt_assertion.rb', line 68

def client_id
  @client_id
end

#expiration_secondsObject (readonly)

Returns the value of attribute expiration_seconds.



68
# File 'lib/safire/jwt_assertion.rb', line 68

attr_reader :client_id, :token_endpoint, :private_key, :kid, :algorithm, :jku, :expiration_seconds

#jkuString? (readonly)

Returns the optional JWKS URL included in the JWT header.

Returns:

  • (String, nil)

    the optional JWKS URL included in the JWT header



68
# File 'lib/safire/jwt_assertion.rb', line 68

attr_reader :client_id, :token_endpoint, :private_key, :kid, :algorithm, :jku, :expiration_seconds

#kidString (readonly)

Returns the key ID matching the public key registered with the authorization server.

Returns:

  • (String)

    the key ID matching the public key registered with the authorization server



68
# File 'lib/safire/jwt_assertion.rb', line 68

attr_reader :client_id, :token_endpoint, :private_key, :kid, :algorithm, :jku, :expiration_seconds

#private_keyOpenSSL::PKey::RSA, OpenSSL::PKey::EC (readonly)

Returns the private key for signing the JWT.

Returns:

  • (OpenSSL::PKey::RSA, OpenSSL::PKey::EC)

    the private key for signing the JWT



68
# File 'lib/safire/jwt_assertion.rb', line 68

attr_reader :client_id, :token_endpoint, :private_key, :kid, :algorithm, :jku, :expiration_seconds

#token_endpointString (readonly)

Returns the token endpoint URL used as aud claim in the JWT.

Returns:

  • (String)

    the token endpoint URL used as aud claim in the JWT



68
# File 'lib/safire/jwt_assertion.rb', line 68

attr_reader :client_id, :token_endpoint, :private_key, :kid, :algorithm, :jku, :expiration_seconds

Instance Method Details

#headerHash

Returns the JWT header.

Returns:

  • (Hash)

    the JWT header with typ, kid, alg, and optional jku



105
106
107
108
109
# File 'lib/safire/jwt_assertion.rb', line 105

def header
  h = { typ: 'JWT', kid: kid, alg: algorithm }
  h[:jku] = jku if jku.present?
  h
end

#payloadHash

Returns the JWT payload.

Returns:

  • (Hash)

    the JWT payload with iss, sub, aud, exp, and jti claims



114
115
116
117
# File 'lib/safire/jwt_assertion.rb', line 114

def payload
  now = Time.now.to_i
  { iss: client_id, sub: client_id, aud: token_endpoint, exp: now + expiration_seconds, jti: generate_jti }
end

#to_jwtString

Generates a signed JWT assertion.

Returns:

  • (String)

    the signed JWT string



98
99
100
# File 'lib/safire/jwt_assertion.rb', line 98

def to_jwt
  JWT.encode(payload, private_key, algorithm, header)
end