Include auth_time in ID token. Switch from upsert -> find_and_create_by so we actually get sid values for consent on the creation of the record
This commit is contained in:
@@ -49,6 +49,9 @@ module Authentication
|
||||
user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
|
||||
Current.session = session
|
||||
|
||||
# Store auth_time in session for OIDC max_age support
|
||||
session[:auth_time] = Time.now.to_i
|
||||
|
||||
# Extract root domain for cross-subdomain cookies (required for forward auth)
|
||||
domain = extract_root_domain(request.host)
|
||||
|
||||
|
||||
@@ -245,15 +245,10 @@ class OidcController < ApplicationController
|
||||
|
||||
# Record user consent
|
||||
requested_scopes = oauth_params['scope'].split(' ')
|
||||
OidcUserConsent.upsert(
|
||||
{
|
||||
user_id: user.id,
|
||||
application_id: application.id,
|
||||
scopes_granted: requested_scopes.join(' '),
|
||||
granted_at: Time.current
|
||||
},
|
||||
unique_by: [:user_id, :application_id]
|
||||
)
|
||||
consent = OidcUserConsent.find_or_initialize_by(user: user, application: application)
|
||||
consent.scopes_granted = requested_scopes.join(' ')
|
||||
consent.granted_at = Time.current
|
||||
consent.save!
|
||||
|
||||
# Generate authorization code
|
||||
auth_code = OidcAuthorizationCode.create!(
|
||||
@@ -416,13 +411,14 @@ class OidcController < ApplicationController
|
||||
return
|
||||
end
|
||||
|
||||
# Generate ID token (JWT) with pairwise SID and at_hash
|
||||
# Generate ID token (JWT) with pairwise SID, at_hash, and auth_time
|
||||
id_token = OidcJwtService.generate_id_token(
|
||||
user,
|
||||
application,
|
||||
consent: consent,
|
||||
nonce: auth_code.nonce,
|
||||
access_token: access_token_record.plaintext_token
|
||||
access_token: access_token_record.plaintext_token,
|
||||
auth_time: session[:auth_time]
|
||||
)
|
||||
|
||||
# Return tokens
|
||||
@@ -539,12 +535,13 @@ class OidcController < ApplicationController
|
||||
return
|
||||
end
|
||||
|
||||
# Generate new ID token (JWT with pairwise SID and at_hash, no nonce for refresh grants)
|
||||
# Generate new ID token (JWT with pairwise SID, at_hash, and auth_time; no nonce for refresh grants)
|
||||
id_token = OidcJwtService.generate_id_token(
|
||||
user,
|
||||
application,
|
||||
consent: consent,
|
||||
access_token: new_access_token.plaintext_token
|
||||
access_token: new_access_token.plaintext_token,
|
||||
auth_time: session[:auth_time]
|
||||
)
|
||||
|
||||
# Return new tokens
|
||||
|
||||
@@ -3,7 +3,7 @@ class OidcJwtService
|
||||
|
||||
class << self
|
||||
# Generate an ID token (JWT) for the user
|
||||
def generate_id_token(user, application, consent: nil, nonce: nil, access_token: nil)
|
||||
def generate_id_token(user, application, consent: nil, nonce: nil, access_token: nil, auth_time: nil)
|
||||
now = Time.current.to_i
|
||||
# Use application's configured ID token TTL (defaults to 1 hour)
|
||||
ttl = application.id_token_expiry_seconds
|
||||
@@ -26,6 +26,9 @@ class OidcJwtService
|
||||
# Add nonce if provided (OIDC requires this for implicit flow)
|
||||
payload[:nonce] = nonce if nonce.present?
|
||||
|
||||
# Add auth_time if provided (OIDC Core §2 - required when max_age is used)
|
||||
payload[:auth_time] = auth_time if auth_time.present?
|
||||
|
||||
# Add at_hash if access token is provided (OIDC Core spec §3.1.3.6)
|
||||
# at_hash = left-most 128 bits of SHA-256 hash of access token, base64url encoded
|
||||
if access_token.present?
|
||||
|
||||
Reference in New Issue
Block a user