Add auth_time, acr and azp support for OIDC claims

This commit is contained in:
Dan Milne
2025-12-31 17:07:54 +11:00
parent fcdd2b6de7
commit d036e25fef
8 changed files with 72 additions and 20 deletions

View File

@@ -44,9 +44,9 @@ module Authentication
final_url
end
def start_new_session_for(user)
def start_new_session_for(user, acr: "1")
user.update!(last_sign_in_at: Time.current)
user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip, acr: acr).tap do |session|
Current.session = session
# Extract root domain for cross-subdomain cookies (required for forward auth)

View File

@@ -163,6 +163,7 @@ class OidcController < ApplicationController
code_challenge: code_challenge,
code_challenge_method: code_challenge_method,
auth_time: Current.session.created_at.to_i,
acr: Current.session.acr,
expires_at: 10.minutes.from_now
)
@@ -261,6 +262,7 @@ class OidcController < ApplicationController
code_challenge: oauth_params['code_challenge'],
code_challenge_method: oauth_params['code_challenge_method'],
auth_time: Current.session.created_at.to_i,
acr: Current.session.acr,
expires_at: 10.minutes.from_now
)
@@ -402,7 +404,8 @@ class OidcController < ApplicationController
user: user,
oidc_access_token: access_token_record,
scope: auth_code.scope,
auth_time: auth_code.auth_time
auth_time: auth_code.auth_time,
acr: auth_code.acr
)
# Find user consent for this application
@@ -414,15 +417,16 @@ class OidcController < ApplicationController
return
end
# Generate ID token (JWT) with pairwise SID, at_hash, and auth_time
# auth_time comes from the authorization code (captured at /authorize time)
# Generate ID token (JWT) with pairwise SID, at_hash, auth_time, and acr
# auth_time and acr come from the authorization code (captured at /authorize time)
id_token = OidcJwtService.generate_id_token(
user,
application,
consent: consent,
nonce: auth_code.nonce,
access_token: access_token_record.plaintext_token,
auth_time: auth_code.auth_time
auth_time: auth_code.auth_time,
acr: auth_code.acr
)
# Return tokens
@@ -528,7 +532,8 @@ class OidcController < ApplicationController
oidc_access_token: new_access_token,
scope: refresh_token_record.scope,
token_family_id: refresh_token_record.token_family_id, # Keep same family for rotation tracking
auth_time: refresh_token_record.auth_time # Carry over original auth_time
auth_time: refresh_token_record.auth_time, # Carry over original auth_time
acr: refresh_token_record.acr # Carry over original acr
)
# Find user consent for this application
@@ -540,14 +545,15 @@ class OidcController < ApplicationController
return
end
# Generate new ID token (JWT with pairwise SID, at_hash, and auth_time; no nonce for refresh grants)
# auth_time comes from the original refresh token (carried over from initial auth)
# Generate new ID token (JWT with pairwise SID, at_hash, auth_time, acr; no nonce for refresh grants)
# auth_time and acr come from the original refresh token (carried over from initial auth)
id_token = OidcJwtService.generate_id_token(
user,
application,
consent: consent,
access_token: new_access_token.plaintext_token,
auth_time: refresh_token_record.auth_time
auth_time: refresh_token_record.auth_time,
acr: refresh_token_record.acr
)
# Return new tokens

View File

@@ -71,8 +71,8 @@ class SessionsController < ApplicationController
return
end
# Sign in successful
start_new_session_for user
# Sign in successful (password only)
start_new_session_for user, acr: "1"
redirect_to after_authentication_url, notice: "Signed in successfully.", allow_other_host: true
end
@@ -101,26 +101,26 @@ class SessionsController < ApplicationController
return
end
# Try TOTP verification first
# Try TOTP verification first (password + TOTP = 2FA)
if user.verify_totp(code)
session.delete(:pending_totp_user_id)
# Restore redirect URL if it was preserved
if session[:totp_redirect_url].present?
session[:return_to_after_authenticating] = session.delete(:totp_redirect_url)
end
start_new_session_for user
start_new_session_for user, acr: "2"
redirect_to after_authentication_url, notice: "Signed in successfully.", allow_other_host: true
return
end
# Try backup code verification
# Try backup code verification (password + backup code = 2FA)
if user.verify_backup_code(code)
session.delete(:pending_totp_user_id)
# Restore redirect URL if it was preserved
if session[:totp_redirect_url].present?
session[:return_to_after_authenticating] = session.delete(:totp_redirect_url)
end
start_new_session_for user
start_new_session_for user, acr: "2"
redirect_to after_authentication_url, notice: "Signed in successfully using backup code.", allow_other_host: true
return
end
@@ -268,8 +268,8 @@ class SessionsController < ApplicationController
session[:return_to_after_authenticating] = session.delete(:webauthn_redirect_url)
end
# Create session
start_new_session_for user
# Create session (WebAuthn/passkey = phishing-resistant, ACR = "2")
start_new_session_for user, acr: "2"
render json: {
success: true,