Class: OmniauthOpenidFederation::FederationEndpoint

Inherits:
Object
  • Object
show all
Defined in:
lib/omniauth_openid_federation/federation_endpoint.rb

Overview

Federation Endpoint for serving entity statements

Supports automatic key provisioning with separate signing and encryption keys. See AUTOMATIC_KEY_PROVISIONING.md for detailed documentation.

Examples:

Auto-configure with separate keys (RECOMMENDED for production)

# In config/initializers/omniauth_openid_federation.rb
OmniauthOpenidFederation::FederationEndpoint.auto_configure(
  issuer: "https://provider.example.com",
  signing_key: OpenSSL::PKey::RSA.new(File.read("config/signing-key.pem")),
  encryption_key: OpenSSL::PKey::RSA.new(File.read("config/encryption-key.pem")),
  entity_statement_path: "config/entity-statement.jwt", # Cache for automatic key rotation
  metadata: {
    openid_relying_party: {
      redirect_uris: ["https://provider.example.com/auth/callback"],
      client_registration_types: ["automatic"]
    }
  },
  auto_provision_keys: true
)

Manual configuration (advanced)

OmniauthOpenidFederation::FederationEndpoint.configure do |config|
  config.issuer = "https://provider.example.com"
  config.subject = "https://provider.example.com"
  config.signing_key = OpenSSL::PKey::RSA.new(File.read("config/signing-key.pem"))
  config.encryption_key = OpenSSL::PKey::RSA.new(File.read("config/encryption-key.pem"))
  config.jwks = {
    keys: [
      { kty: "RSA", use: "sig", kid: "sig-key-id", n: "...", e: "..." },
      { kty: "RSA", use: "enc", kid: "enc-key-id", n: "...", e: "..." }
    ]
  }
  config. = {
    openid_provider: {
      issuer: "https://provider.example.com",
      authorization_endpoint: "https://provider.example.com/oauth2/authorize",
      token_endpoint: "https://provider.example.com/oauth2/token",
      userinfo_endpoint: "https://provider.example.com/oauth2/userinfo",
      jwks_uri: "https://provider.example.com/.well-known/jwks.json",
      signed_jwks_uri: "https://provider.example.com/.well-known/signed-jwks.json"
    }
  }
end

# In config/routes.rb (Rails)
get "/.well-known/openid-federation", to: "omniauth_openid_federation/federation#show"

Defined Under Namespace

Classes: Configuration

Class Method Summary collapse

Class Method Details

.auto_configure(issuer:, signing_key: nil, encryption_key: nil, private_key: nil, jwks: nil, subject: nil, entity_statement_path: nil, entity_statement_url: nil, metadata: nil, expiration_seconds: nil, jwks_cache_ttl: nil, auto_provision_keys: true, key_rotation_period: nil) ⇒ Configuration

Auto-configure the federation endpoint with automatic key provisioning Automatically calculates JWKS, metadata, and other settings from provided inputs

Automatic Key Provisioning:

  • Extracts JWKS from entity_statement_path if provided (cached, supports key rotation)

  • Supports separate signing_key and encryption_key (RECOMMENDED for production)

  • Falls back to single private_key (DEV/TESTING ONLY - not recommended for production)

  • Automatically generates both signing and encryption keys from provided keys

Raises:



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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/omniauth_openid_federation/federation_endpoint.rb', line 113

def auto_configure(
  issuer:,
  signing_key: nil,
  encryption_key: nil,
  private_key: nil,
  jwks: nil,
  subject: nil,
  entity_statement_path: nil,
  entity_statement_url: nil,
  metadata: nil,
  expiration_seconds: nil,
  jwks_cache_ttl: nil,
  auto_provision_keys: true,
  key_rotation_period: nil
)
  raise ConfigurationError, "Issuer is required" if StringHelpers.blank?(issuer)

  if signing_key && encryption_key && private_key
    raise ConfigurationError, "Cannot specify signing_key, encryption_key, and private_key simultaneously. " \
      "Use either (signing_key + encryption_key) OR private_key, not both."
  end

  unless auto_provision_keys
    if signing_key.nil? && encryption_key.nil? && private_key.nil? && jwks.nil?
      raise ConfigurationError, "At least one key source is required: signing_key, encryption_key, private_key, or jwks"
    end
  end

  # Warn if using single private_key (dev/testing only)
  if private_key && signing_key.nil? && encryption_key.nil?
    OmniauthOpenidFederation::Logger.warn(
      "[FederationEndpoint] Using single private_key for both signing and encryption. " \
      "This is DEV/TESTING ONLY. For production, use separate signing_key and encryption_key."
    )
  end

  config = configuration

  config.issuer = issuer
  config.subject = subject || issuer

  if auto_provision_keys && jwks.nil?
    jwks = provision_jwks(
      signing_key: signing_key,
      encryption_key: encryption_key,
      private_key: private_key,
      entity_statement_path: entity_statement_path,
      issuer: issuer,
      subject: subject || issuer,
      metadata: ,
      entity_statement_path_provided: !entity_statement_path.nil?
    )

    if jwks.nil? && signing_key.nil? && encryption_key.nil? && private_key.nil? && config.signing_key.nil?
      raise ConfigurationError, "Signing key is required. Provide signing_key, encryption_key, or private_key, or enable auto_provision_keys with entity_statement_path."
    end
  end

  config.jwks = jwks || raise(ConfigurationError, "JWKS is required. Provide jwks parameter or enable auto_provision_keys.")

  if signing_key && encryption_key
    config.signing_key = signing_key
    config.encryption_key = encryption_key
    config.private_key = signing_key
  elsif signing_key
    config.signing_key = signing_key
    config.encryption_key = signing_key
    config.private_key = signing_key
  elsif private_key
    config.private_key = private_key
    config.signing_key = private_key
    config.encryption_key = private_key
  elsif config.signing_key && config.encryption_key
    config.private_key = config.signing_key
  elsif config.signing_key
    config.private_key = config.signing_key
    config.encryption_key = config.signing_key
  else
    raise ConfigurationError, "Signing key is required. Provide signing_key, encryption_key, or private_key, or enable auto_provision_keys with entity_statement_path."
  end

  keys = config.jwks[:keys] || config.jwks["keys"] || []
  signing_key_jwk = keys.find { |k| (k[:use] || k["use"]) == "sig" } || keys.first
  config.kid = signing_key_jwk&.dig(:kid) || signing_key_jwk&.dig("kid")

  entity_type = detect_entity_type()

  if 
     = ensure_jwks_endpoints(, issuer, entity_type)
    config. = 
    entity_type = detect_entity_type(config.)
  else
     = {
      issuer: issuer
    }

    if entity_type == :openid_provider
      [:federation_fetch_endpoint] = "#{issuer}/.well-known/openid-federation/fetch"
      config. = {
        openid_provider: 
      }
    else
      config. = {
        openid_relying_party: 
      }
    end

    config. = ensure_jwks_endpoints(config., issuer, entity_type)

    OmniauthOpenidFederation::Logger.warn(
      "[FederationEndpoint] Auto-generated metadata only includes well-known endpoints. " \
      "Provide custom metadata parameter for application-specific endpoints " \
      "(authorization_endpoint, token_endpoint, userinfo_endpoint for OP; " \
      "redirect_uris, client_registration_types for RP)."
    )
  end

  config.entity_type = entity_type

  config.expiration_seconds = expiration_seconds if expiration_seconds
  config.jwks_cache_ttl = jwks_cache_ttl if jwks_cache_ttl
  config.key_rotation_period = key_rotation_period if key_rotation_period
  config.entity_statement_path = entity_statement_path if entity_statement_path

  if entity_statement_path && (signing_key || private_key)
    begin
      keys_dir = File.dirname(entity_statement_path)
      FileUtils.mkdir_p(keys_dir) unless File.directory?(keys_dir)

      if signing_key && encryption_key
        signing_key_path = File.join(keys_dir, ".federation-signing-key.pem")
        encryption_key_path = File.join(keys_dir, ".federation-encryption-key.pem")
        File.write(signing_key_path, signing_key.to_pem)
        File.write(encryption_key_path, encryption_key.to_pem)
        File.chmod(0o600, signing_key_path)
        File.chmod(0o600, encryption_key_path)
        OmniauthOpenidFederation::Logger.debug("[FederationEndpoint] Saved provided signing and encryption keys to disk")
      elsif private_key
        signing_key_path = File.join(keys_dir, ".federation-signing-key.pem")
        encryption_key_path = File.join(keys_dir, ".federation-encryption-key.pem")
        File.write(signing_key_path, private_key.to_pem)
        File.write(encryption_key_path, private_key.to_pem)
        File.chmod(0o600, signing_key_path)
        File.chmod(0o600, encryption_key_path)
        OmniauthOpenidFederation::Logger.debug("[FederationEndpoint] Saved provided private_key to disk (used for both signing and encryption)")
      end

      entity_statement = generate_entity_statement
      FileUtils.mkdir_p(File.dirname(entity_statement_path)) if File.dirname(entity_statement_path) != "."
      File.write(entity_statement_path, entity_statement)
      File.chmod(0o600, entity_statement_path) if File.exist?(entity_statement_path)
      OmniauthOpenidFederation::Logger.debug("[FederationEndpoint] Regenerated entity statement with provided keys")
    rescue => e
      OmniauthOpenidFederation::Logger.warn("[FederationEndpoint] Failed to save keys or regenerate entity statement: #{e.message}")
    end
  end

  if auto_provision_keys && entity_statement_path && config.key_rotation_period
    rotate_keys_if_needed(config)
  end

  OmniauthOpenidFederation::Logger.info("[FederationEndpoint] Auto-configured with issuer: #{issuer}")
  config
end

.configurationObject



384
385
386
# File 'lib/omniauth_openid_federation/federation_endpoint.rb', line 384

def configuration
  @configuration ||= Configuration.new
end

.configure {|config| ... } ⇒ Object

Configure the federation endpoint

Yields:

  • (config)

    Configuration block

Yield Parameters:



75
76
77
78
# File 'lib/omniauth_openid_federation/federation_endpoint.rb', line 75

def configure
  yield(configuration) if block_given?
  configuration
end

.current_jwksObject



439
440
441
442
443
# File 'lib/omniauth_openid_federation/federation_endpoint.rb', line 439

def current_jwks
  config = configuration
  validate_configuration(config)
  resolve_current_jwks(config)
end

.ensure_jwks_endpoints(metadata, issuer, entity_type) ⇒ Object



612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
# File 'lib/omniauth_openid_federation/federation_endpoint.rb', line 612

def ensure_jwks_endpoints(, issuer, entity_type)
   = .dup
  entity_type ||= detect_entity_type()

  section = if entity_type == :openid_provider
    [:openid_provider] || ["openid_provider"] || {}
  else
    [:openid_relying_party] || ["openid_relying_party"] || {}
  end

  section = section.each_with_object({}) { |(k, v), h| h[k.to_sym] = v }

  section[:jwks_uri] ||= "#{issuer}/.well-known/jwks.json"
  section[:signed_jwks_uri] ||= "#{issuer}/.well-known/signed-jwks.json"

  if entity_type == :openid_provider
    section[:federation_fetch_endpoint] ||= "#{issuer}/.well-known/openid-federation/fetch"
  end

  if entity_type == :openid_provider
    [:openid_provider] = section
    .delete("openid_provider") if .key?("openid_provider")
  else
    [:openid_relying_party] = section
    .delete("openid_relying_party") if .key?("openid_relying_party")
  end

  
end

.generate_entity_statementObject



388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# File 'lib/omniauth_openid_federation/federation_endpoint.rb', line 388

def generate_entity_statement
  config = configuration
  validate_configuration(config)

  builder = Federation::EntityStatementBuilder.new(
    issuer: config.issuer,
    subject: config.subject || config.issuer,
    private_key: config.private_key,
    jwks: config.jwks,
    metadata: config.,
    expiration_seconds: config.expiration_seconds || 86400,
    kid: config.kid,
    authority_hints: config.authority_hints
  )

  builder.build
end

.generate_fresh_keys(entity_statement_path:, issuer: nil, subject: nil, metadata: nil, keys_output_dir: nil) ⇒ Hash?

Generate fresh signing and encryption keys and write entity statement to file



491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
# File 'lib/omniauth_openid_federation/federation_endpoint.rb', line 491

def generate_fresh_keys(entity_statement_path:, issuer: nil, subject: nil, metadata: nil, keys_output_dir: nil)
  signing_key = OpenSSL::PKey::RSA.new(2048)
  encryption_key = OpenSSL::PKey::RSA.new(2048)

  signing_jwk = OmniauthOpenidFederation::Utils.rsa_key_to_jwk(signing_key, use: "sig")
  encryption_jwk = OmniauthOpenidFederation::Utils.rsa_key_to_jwk(encryption_key, use: "enc")
  jwks = {keys: [signing_jwk, encryption_jwk]}

  config = configuration
  issuer ||= config.issuer
  subject ||= config.subject || issuer
   ||= config.

  unless 
    if issuer
      # Default to openid_relying_party (RP) entity type for clients
       = {
        openid_relying_party: {
          issuer: issuer,
          jwks_uri: "#{issuer}/.well-known/jwks.json",
          signed_jwks_uri: "#{issuer}/.well-known/signed-jwks.json"
        }
      }
      OmniauthOpenidFederation::Logger.debug("[FederationEndpoint] Generated minimal metadata for key generation")
    else
      OmniauthOpenidFederation::Logger.warn("[FederationEndpoint] Cannot generate entity statement: issuer missing")
      return nil
    end
  end

  if issuer
    builder = Federation::EntityStatementBuilder.new(
      issuer: issuer,
      subject: subject,
      private_key: signing_key, # Use signing key for entity statement signature
      jwks: jwks,
      metadata: ,
      expiration_seconds: config.expiration_seconds || 86400,
      kid: signing_jwk[:kid] || signing_jwk["kid"]
    )

    entity_statement = builder.build

    keys_dir = keys_output_dir || File.dirname(entity_statement_path)
    FileUtils.mkdir_p(keys_dir) unless File.directory?(keys_dir)

    signing_key_path = File.join(keys_dir, ".federation-signing-key.pem")
    encryption_key_path = File.join(keys_dir, ".federation-encryption-key.pem")

    File.write(signing_key_path, signing_key.to_pem)
    File.write(encryption_key_path, encryption_key.to_pem)
    File.chmod(0o600, signing_key_path)
    File.chmod(0o600, encryption_key_path)

    FileUtils.mkdir_p(File.dirname(entity_statement_path)) if File.dirname(entity_statement_path) != "."
    File.write(entity_statement_path, entity_statement)
    File.chmod(0o600, entity_statement_path) if File.exist?(entity_statement_path)

    config.signing_key = signing_key
    config.encryption_key = encryption_key
    config.private_key = signing_key

    OmniauthOpenidFederation::Logger.info(
      "[FederationEndpoint] Generated fresh keys and wrote entity statement to: #{OmniauthOpenidFederation::Utils.sanitize_path(entity_statement_path)}"
    )
    OmniauthOpenidFederation::Logger.info(
      "[FederationEndpoint] Private keys stored in: #{OmniauthOpenidFederation::Utils.sanitize_path(keys_dir)}"
    )
    jwks
  else
    OmniauthOpenidFederation::Logger.warn("[FederationEndpoint] Cannot generate entity statement: issuer missing")
    nil
  end
rescue => e
  OmniauthOpenidFederation::Logger.error("[FederationEndpoint] Failed to generate fresh keys: #{e.message}")
  nil
end

.generate_signed_jwksObject



406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
# File 'lib/omniauth_openid_federation/federation_endpoint.rb', line 406

def generate_signed_jwks
  config = configuration
  validate_configuration(config)

  jwks_payload = resolve_signed_jwks_payload(config)

  signing_kid = config.signed_jwks_signing_kid || config.kid || extract_kid_from_jwks(config.jwks)
  expiration_seconds = config.signed_jwks_expiration_seconds || 86400

  now = Time.now.to_i
  payload = {
    iss: config.issuer,
    sub: config.subject || config.issuer,
    iat: now,
    exp: now + expiration_seconds,
    jwks: jwks_payload
  }

  header = {
    alg: "RS256",
    typ: "JWT",
    kid: signing_kid
  }

  begin
    JWT.encode(payload, config.private_key, "RS256", header)
  rescue => e
    error_msg = "Failed to sign JWKS: #{e.class} - #{e.message}"
    OmniauthOpenidFederation::Logger.error("[FederationEndpoint] #{error_msg}")
    raise SignatureError, error_msg, e.backtrace
  end
end

.get_subordinate_statement(subject_entity_id) ⇒ Object



642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
# File 'lib/omniauth_openid_federation/federation_endpoint.rb', line 642

def get_subordinate_statement(subject_entity_id)
  config = configuration
  validate_configuration(config)

  entity_type = detect_entity_type(config.)
  unless entity_type == :openid_provider
    OmniauthOpenidFederation::Logger.debug("[FederationEndpoint] Fetch endpoint called for non-OP entity (#{entity_type}), returning nil")
    return nil
  end

  if config.subordinate_statements_proc
    return config.subordinate_statements_proc.call(subject_entity_id)
  end

  if config.subordinate_statements && config.subordinate_statements[subject_entity_id]
    subordinate_config = config.subordinate_statements[subject_entity_id]
    return generate_subordinate_statement(
      subject_entity_id: subject_entity_id,
      subject_metadata: subordinate_config[:metadata] || subordinate_config["metadata"],
      metadata_policy: subordinate_config[:metadata_policy] || subordinate_config["metadata_policy"],
      constraints: subordinate_config[:constraints] || subordinate_config["constraints"]
    )
  end

  nil
end

.provision_jwks(signing_key: nil, encryption_key: nil, private_key: nil, entity_statement_path: nil, issuer: nil, subject: nil, metadata: nil, entity_statement_path_provided: false) ⇒ Hash?

Automatic key provisioning: Extract or generate JWKS from available sources



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/omniauth_openid_federation/federation_endpoint.rb', line 289

def provision_jwks(signing_key: nil, encryption_key: nil, private_key: nil, entity_statement_path: nil, issuer: nil, subject: nil, metadata: nil, entity_statement_path_provided: false)
  if encryption_key
    signing_key_for_jwk = signing_key || private_key
    raise ConfigurationError, "Signing key is required when encryption_key is provided. Provide signing_key or private_key." unless signing_key_for_jwk

    # If same, generate single JWK to avoid duplicate kid values
    if signing_key_for_jwk.public_key.to_pem == encryption_key.public_key.to_pem
      single_jwk = OmniauthOpenidFederation::Utils.rsa_key_to_jwk(signing_key_for_jwk, use: nil)
      return {keys: [single_jwk]}
    else
      signing_jwk = OmniauthOpenidFederation::Utils.rsa_key_to_jwk(signing_key_for_jwk, use: "sig")
      encryption_jwk = OmniauthOpenidFederation::Utils.rsa_key_to_jwk(encryption_key, use: "enc")
      return {keys: [signing_jwk, encryption_jwk]}
    end
  elsif private_key || signing_key
    single_key = private_key || signing_key

    # Generate JWK without 'use' field to avoid duplicate kid values which violate the spec
    single_jwk = OmniauthOpenidFederation::Utils.rsa_key_to_jwk(single_key, use: nil)
    return {keys: [single_jwk]}
  end

  extraction_failed = false
  if entity_statement_path&.then { |path| File.exist?(path) }
    begin
      entity_statement_content = File.read(entity_statement_path)
      jwks = OmniauthOpenidFederation::Utils.extract_jwks_from_entity_statement(entity_statement_content)
      if jwks&.dig(:keys)&.any?
        OmniauthOpenidFederation::Logger.debug("[FederationEndpoint] Extracted JWKS from entity statement file: #{entity_statement_path}")

        keys_dir = File.dirname(entity_statement_path)
        signing_key_path = File.join(keys_dir, ".federation-signing-key.pem")
        encryption_key_path = File.join(keys_dir, ".federation-encryption-key.pem")

        if File.exist?(signing_key_path) && File.exist?(encryption_key_path)
          begin
            config = configuration
            config.signing_key = OpenSSL::PKey::RSA.new(File.read(signing_key_path))
            config.encryption_key = OpenSSL::PKey::RSA.new(File.read(encryption_key_path))
            config.private_key = config.signing_key
            OmniauthOpenidFederation::Logger.debug("[FederationEndpoint] Loaded private keys from disk")
          rescue => e
            OmniauthOpenidFederation::Logger.warn("[FederationEndpoint] Failed to load private keys from disk: #{e.message}")
          end
        elsif File.exist?(signing_key_path)
          # Single key file (backward compatibility or dev/testing)
          begin
            config = configuration
            single_key = OpenSSL::PKey::RSA.new(File.read(signing_key_path))
            config.signing_key = single_key
            config.encryption_key = single_key
            config.private_key = single_key
            OmniauthOpenidFederation::Logger.debug("[FederationEndpoint] Loaded single private key from disk")
          rescue => e
            OmniauthOpenidFederation::Logger.warn("[FederationEndpoint] Failed to load private key from disk: #{e.message}")
          end
        end

        return jwks
      else
        extraction_failed = true
      end
    rescue => e
      OmniauthOpenidFederation::Logger.warn("[FederationEndpoint] Failed to extract JWKS from entity statement file: #{e.message}")
      extraction_failed = true
    end
  end

  if issuer && (!entity_statement_path_provided || !extraction_failed)
    entity_statement_path ||= begin
      configuration
      if defined?(Rails) && Rails.root
        default_path = Rails.root.join("config/.federation-entity-statement.jwt").to_s
        OmniauthOpenidFederation::Logger.info("[FederationEndpoint] No entity_statement_path provided, using default: #{OmniauthOpenidFederation::Utils.sanitize_path(default_path)}")
        default_path
      end
    end

    if entity_statement_path
      OmniauthOpenidFederation::Logger.info("[FederationEndpoint] No keys provided, auto-generating new signing and encryption keys")
      jwks = generate_fresh_keys(
        entity_statement_path: entity_statement_path,
        issuer: issuer,
        subject: subject || issuer,
        metadata:  # Can be nil - generate_fresh_keys will create minimal metadata
      )
      return jwks if jwks
    else
      OmniauthOpenidFederation::Logger.warn("[FederationEndpoint] Cannot auto-generate keys: entity_statement_path is required for persistence")
    end
  end

  nil
end

.rack_appRackEndpoint

Get a Rack-compatible endpoint handler Use this for framework-agnostic routing (Sinatra, Rack, etc.)

Examples:

Using with Sinatra

require "sinatra"
require "omniauth_openid_federation"

use OmniauthOpenidFederation::FederationEndpoint.rack_app

Using with plain Rack

require "rack"
require "omniauth_openid_federation"

app = Rack::Builder.new do
  map "/.well-known" do
    run OmniauthOpenidFederation::FederationEndpoint.rack_app
  end
end


464
465
466
467
# File 'lib/omniauth_openid_federation/federation_endpoint.rb', line 464

def rack_app
  require_relative "rack_endpoint"
  RackEndpoint.new
end

.rotate_keys_if_needed(config) ⇒ Object



569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
# File 'lib/omniauth_openid_federation/federation_endpoint.rb', line 569

def rotate_keys_if_needed(config)
  return unless config.key_rotation_period && config.entity_statement_path

  entity_statement_path = config.entity_statement_path
  return unless File.exist?(entity_statement_path)

  file_mtime = File.mtime(entity_statement_path)
  rotation_period_seconds = config.key_rotation_period.to_i
  time_since_rotation = TimeHelpers.now - file_mtime

  if time_since_rotation >= rotation_period_seconds
    OmniauthOpenidFederation::Logger.info(
      "[FederationEndpoint] Key rotation period elapsed (#{time_since_rotation.to_i}s >= #{rotation_period_seconds}s), " \
      "generating new keys"
    )

    keys_dir = File.dirname(entity_statement_path)
    jwks = generate_fresh_keys(
      entity_statement_path: entity_statement_path,
      issuer: config.issuer,
      subject: config.subject,
      metadata: config.,
      keys_output_dir: keys_dir
    )

    if jwks
      config.jwks = jwks
      keys = jwks[:keys] || jwks["keys"] || []
      signing_key_jwk = keys.find { |k| (k[:use] || k["use"]) == "sig" } || keys.first
      config.kid = signing_key_jwk&.dig(:kid) || signing_key_jwk&.dig("kid")

      OmniauthOpenidFederation::Logger.info("[FederationEndpoint] Keys rotated successfully")
    else
      OmniauthOpenidFederation::Logger.warn("[FederationEndpoint] Key rotation failed, using existing keys")
    end
  else
    OmniauthOpenidFederation::Logger.debug(
      "[FederationEndpoint] Keys still valid (#{time_since_rotation.to_i}s < #{rotation_period_seconds}s), " \
      "no rotation needed"
    )
  end
end