We already have a login_time stored - the time stamp of the Session instance creation ( created after successful login ).
This commit is contained in:
@@ -49,9 +49,6 @@ module Authentication
|
|||||||
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).tap do |session|
|
||||||
Current.session = 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)
|
# Extract root domain for cross-subdomain cookies (required for forward auth)
|
||||||
domain = extract_root_domain(request.host)
|
domain = extract_root_domain(request.host)
|
||||||
|
|
||||||
|
|||||||
@@ -412,13 +412,14 @@ class OidcController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Generate ID token (JWT) with pairwise SID, at_hash, and auth_time
|
# Generate ID token (JWT) with pairwise SID, at_hash, and auth_time
|
||||||
|
# auth_time comes from the Session model's created_at (when user logged in)
|
||||||
id_token = OidcJwtService.generate_id_token(
|
id_token = OidcJwtService.generate_id_token(
|
||||||
user,
|
user,
|
||||||
application,
|
application,
|
||||||
consent: consent,
|
consent: consent,
|
||||||
nonce: auth_code.nonce,
|
nonce: auth_code.nonce,
|
||||||
access_token: access_token_record.plaintext_token,
|
access_token: access_token_record.plaintext_token,
|
||||||
auth_time: session[:auth_time]
|
auth_time: Current.session.created_at.to_i
|
||||||
)
|
)
|
||||||
|
|
||||||
# Return tokens
|
# Return tokens
|
||||||
@@ -536,12 +537,13 @@ class OidcController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Generate new ID token (JWT with pairwise SID, at_hash, and auth_time; no nonce for refresh grants)
|
# Generate new ID token (JWT with pairwise SID, at_hash, and auth_time; no nonce for refresh grants)
|
||||||
|
# auth_time comes from the Session model's created_at (when user logged in)
|
||||||
id_token = OidcJwtService.generate_id_token(
|
id_token = OidcJwtService.generate_id_token(
|
||||||
user,
|
user,
|
||||||
application,
|
application,
|
||||||
consent: consent,
|
consent: consent,
|
||||||
access_token: new_access_token.plaintext_token,
|
access_token: new_access_token.plaintext_token,
|
||||||
auth_time: session[:auth_time]
|
auth_time: Current.session.created_at.to_i
|
||||||
)
|
)
|
||||||
|
|
||||||
# Return new tokens
|
# Return new tokens
|
||||||
|
|||||||
@@ -532,7 +532,7 @@ class OidcPkceControllerTest < ActionDispatch::IntegrationTest
|
|||||||
# AUTH_TIME CLAIM TESTS
|
# AUTH_TIME CLAIM TESTS
|
||||||
# ====================
|
# ====================
|
||||||
|
|
||||||
test "ID token includes auth_time claim from session" do
|
test "ID token includes auth_time claim from session created_at" do
|
||||||
# Create consent
|
# Create consent
|
||||||
OidcUserConsent.create!(
|
OidcUserConsent.create!(
|
||||||
user: @user,
|
user: @user,
|
||||||
@@ -548,8 +548,8 @@ class OidcPkceControllerTest < ActionDispatch::IntegrationTest
|
|||||||
.tr("+/", "-_")
|
.tr("+/", "-_")
|
||||||
.tr("=", "")
|
.tr("=", "")
|
||||||
|
|
||||||
# Set auth_time in session (simulating user login)
|
# Get the expected auth_time from the session's created_at
|
||||||
session[:auth_time] = Time.now.to_i - 300 # 5 minutes ago
|
expected_auth_time = Current.session.created_at.to_i
|
||||||
|
|
||||||
# Create authorization code
|
# Create authorization code
|
||||||
auth_code = OidcAuthorizationCode.create!(
|
auth_code = OidcAuthorizationCode.create!(
|
||||||
@@ -577,10 +577,10 @@ class OidcPkceControllerTest < ActionDispatch::IntegrationTest
|
|||||||
tokens = JSON.parse(@response.body)
|
tokens = JSON.parse(@response.body)
|
||||||
assert tokens.key?("id_token")
|
assert tokens.key?("id_token")
|
||||||
|
|
||||||
# Decode and verify auth_time is present
|
# Decode and verify auth_time is present and matches session created_at
|
||||||
decoded = JWT.decode(tokens["id_token"], nil, false).first
|
decoded = JWT.decode(tokens["id_token"], nil, false).first
|
||||||
assert_includes decoded.keys, "auth_time", "ID token should include auth_time"
|
assert_includes decoded.keys, "auth_time", "ID token should include auth_time"
|
||||||
assert_equal session[:auth_time], decoded["auth_time"], "auth_time should match session value"
|
assert_equal expected_auth_time, decoded["auth_time"], "auth_time should match session created_at"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "ID token includes auth_time in refresh token flow" do
|
test "ID token includes auth_time in refresh token flow" do
|
||||||
@@ -593,8 +593,8 @@ class OidcPkceControllerTest < ActionDispatch::IntegrationTest
|
|||||||
sid: "test-sid-refresh-auth-time"
|
sid: "test-sid-refresh-auth-time"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set auth_time in session
|
# Get the expected auth_time from the session's created_at
|
||||||
session[:auth_time] = Time.now.to_i - 600 # 10 minutes ago
|
expected_auth_time = Current.session.created_at.to_i
|
||||||
|
|
||||||
# Create initial access and refresh tokens (bypass PKCE for this test)
|
# Create initial access and refresh tokens (bypass PKCE for this test)
|
||||||
auth_code = OidcAuthorizationCode.create!(
|
auth_code = OidcAuthorizationCode.create!(
|
||||||
@@ -638,10 +638,10 @@ class OidcPkceControllerTest < ActionDispatch::IntegrationTest
|
|||||||
new_tokens = JSON.parse(@response.body)
|
new_tokens = JSON.parse(@response.body)
|
||||||
assert new_tokens.key?("id_token")
|
assert new_tokens.key?("id_token")
|
||||||
|
|
||||||
# Decode and verify auth_time is still present from refresh
|
# Decode and verify auth_time is still present from session created_at
|
||||||
decoded = JWT.decode(new_tokens["id_token"], nil, false).first
|
decoded = JWT.decode(new_tokens["id_token"], nil, false).first
|
||||||
assert_includes decoded.keys, "auth_time", "Refreshed ID token should include auth_time"
|
assert_includes decoded.keys, "auth_time", "Refreshed ID token should include auth_time"
|
||||||
assert_equal session[:auth_time], decoded["auth_time"], "auth_time should persist from original session"
|
assert_equal expected_auth_time, decoded["auth_time"], "auth_time should match session created_at"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "at_hash is correctly computed and included in ID token" do
|
test "at_hash is correctly computed and included in ID token" do
|
||||||
|
|||||||
Reference in New Issue
Block a user