Class: OmniauthOpenidFederation::Jws
- Inherits:
-
Object
- Object
- OmniauthOpenidFederation::Jws
- Defined in:
- lib/omniauth_openid_federation/jws.rb
Overview
JWT Request Object builder for signed authorization requests
Constant Summary collapse
- STATE_BYTES =
State generation constants
16
Instance Attribute Summary collapse
-
#nonce ⇒ Object
Number of hex bytes for state parameter.
-
#private_key ⇒ Object
Number of hex bytes for state parameter.
-
#state ⇒ Object
Number of hex bytes for state parameter.
Instance Method Summary collapse
-
#add_claim(key, value) ⇒ Object
Add a custom claim to the JWT.
-
#initialize(client_id:, redirect_uri:, scope: "openid", issuer: nil, audience: nil, state: nil, nonce: nil, response_type: "code", response_mode: nil, login_hint: nil, ui_locales: nil, claims_locales: nil, prompt: nil, hd: nil, acr_values: nil, extra_params: {}, private_key: nil, jwks: nil, entity_statement_path: nil, key_source: :local, client_entity_statement: nil) ⇒ Jws
constructor
Initialize JWT request object builder.
-
#sign(provider_metadata: nil, always_encrypt: false) ⇒ String
Sign the request object JWT.
Constructor Details
#initialize(client_id:, redirect_uri:, scope: "openid", issuer: nil, audience: nil, state: nil, nonce: nil, response_type: "code", response_mode: nil, login_hint: nil, ui_locales: nil, claims_locales: nil, prompt: nil, hd: nil, acr_values: nil, extra_params: {}, private_key: nil, jwks: nil, entity_statement_path: nil, key_source: :local, client_entity_statement: nil) ⇒ Jws
Initialize JWT request object builder
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/omniauth_openid_federation/jws.rb', line 86 def initialize( client_id:, redirect_uri:, scope: "openid", issuer: nil, audience: nil, state: nil, nonce: nil, response_type: "code", response_mode: nil, login_hint: nil, ui_locales: nil, claims_locales: nil, prompt: nil, hd: nil, acr_values: nil, extra_params: {}, private_key: nil, jwks: nil, entity_statement_path: nil, key_source: :local, client_entity_statement: nil ) # Security: User input parameters are validated and sanitized before reaching here # Configuration parameters are trusted and only trimmed for consistency # Store trimmed values to ensure consistency @client_id = client_id.to_s.strip @redirect_uri = redirect_uri.to_s.strip @scope = scope.to_s.strip @issuer = issuer&.to_s&.strip @audience = audience&.to_s&.strip @state = state&.to_s&.strip || SecureRandom.hex(STATE_BYTES) @nonce = nonce&.to_s&.strip @response_type = response_type.to_s.strip @response_mode = response_mode&.to_s&.strip # User input parameters (already sanitized) @login_hint = login_hint&.to_s&.strip @ui_locales = ui_locales&.to_s&.strip @claims_locales = claims_locales&.to_s&.strip # Configuration parameters (trusted, only trimmed) @prompt = prompt&.to_s&.strip @hd = hd&.to_s&.strip # User input parameter (already sanitized) @acr_values = acr_values&.to_s&.strip @extra_params = extra_params @jwks = jwks @entity_statement_path = entity_statement_path @key_source = key_source @client_entity_statement = client_entity_statement # Extract signing key based on key_source configuration # :local - Use local static private_key directly (for current setup) # :federation - Use federation/JWKS from entity statement first, fallback to private_key # According to OpenID Federation spec: supports separate signing/encryption keys if @key_source == :federation # Try federation/JWKS from entity statement first, then fallback to local private_key = if @entity_statement_path @private_key = KeyExtractor.extract_signing_key( jwks: @jwks, metadata: , private_key: private_key ) || private_key else # :local - Use local private_key directly, ignore JWKS/metadata @private_key = private_key end end |
Instance Attribute Details
#nonce ⇒ Object
Number of hex bytes for state parameter
61 62 63 |
# File 'lib/omniauth_openid_federation/jws.rb', line 61 def nonce @nonce end |
#private_key ⇒ Object
Number of hex bytes for state parameter
61 62 63 |
# File 'lib/omniauth_openid_federation/jws.rb', line 61 def private_key @private_key end |
#state ⇒ Object
Number of hex bytes for state parameter
61 62 63 |
# File 'lib/omniauth_openid_federation/jws.rb', line 61 def state @state end |
Instance Method Details
#add_claim(key, value) ⇒ Object
Add a custom claim to the JWT
158 159 160 |
# File 'lib/omniauth_openid_federation/jws.rb', line 158 def add_claim(key, value) @extra_params[key] = value end |
#sign(provider_metadata: nil, always_encrypt: false) ⇒ String
Sign the request object JWT
Required for secure authorization requests per RFC 9101. All authentication requests MUST use signed request objects. This method enforces this requirement - unsigned requests are NOT allowed.
According to OpenID Connect Core and RFC 9101, request objects can be:
- Signed only (default)
- Signed and encrypted (if provider metadata specifies encryption)
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/omniauth_openid_federation/jws.rb', line 175 def sign(provider_metadata: nil, always_encrypt: false) # ENFORCE: Private key is MANDATORY - no bypass possible Validators.validate_private_key!(@private_key) begin signed_jwt = build_jwt unless OmniauthOpenidFederation::StringHelpers.present?(signed_jwt) error_msg = "Failed to sign JWT request object - signed request objects are MANDATORY" OmniauthOpenidFederation::Logger.error("[Jws] #{error_msg}") raise SecurityError, error_msg end # Extract kid from header for logging header_part = signed_jwt.split(".").first header = JSON.parse(Base64.urlsafe_decode64(header_part)) kid = header["kid"] OmniauthOpenidFederation::Logger.debug("[Jws] Successfully signed request object with kid: #{kid}") # Encrypt if required (provider metadata specifies encryption OR always_encrypt option is true) # According to RFC 9101 and OpenID Connect Core, if provider specifies # request_object_encryption_alg, the client SHOULD encrypt request objects if should_encrypt_request_object?(, always_encrypt: always_encrypt) encrypted_jwt = encrypt_request_object(signed_jwt, ) OmniauthOpenidFederation::Logger.debug("[Jws] Successfully encrypted request object") encrypted_jwt else signed_jwt end rescue => e error_msg = "Failed to sign JWT request object (required for secure authorization): #{e.class} - #{e.}" OmniauthOpenidFederation::Logger.error("[Jws] #{error_msg}") raise SignatureError, error_msg, e.backtrace end end |