Include the hash of the access token in the JWT / ID Token under the key at_hash as per the requirements. Update the discovery endpoint to describe subject_type as 'pairwise', rather than 'public', since we do pairwise subject ids.
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / system-test (push) Has been cancelled

This commit is contained in:
Dan Milne
2025-12-31 14:45:38 +11:00
parent 40815d3576
commit ed7ceedef5
3 changed files with 44 additions and 6 deletions

View File

@@ -26,7 +26,7 @@ class OidcController < ApplicationController
response_types_supported: ["code"],
response_modes_supported: ["query"],
grant_types_supported: ["authorization_code", "refresh_token"],
subject_types_supported: ["public"],
subject_types_supported: ["pairwise"],
id_token_signing_alg_values_supported: ["RS256"],
scopes_supported: ["openid", "profile", "email", "groups", "offline_access"],
token_endpoint_auth_methods_supported: ["client_secret_post", "client_secret_basic"],
@@ -422,8 +422,14 @@ class OidcController < ApplicationController
return
end
# Generate ID token (JWT) with pairwise SID
id_token = OidcJwtService.generate_id_token(user, application, consent: consent, nonce: auth_code.nonce)
# Generate ID token (JWT) with pairwise SID and at_hash
id_token = OidcJwtService.generate_id_token(
user,
application,
consent: consent,
nonce: auth_code.nonce,
access_token: access_token_record.plaintext_token
)
# Return tokens
render json: {
@@ -539,8 +545,13 @@ class OidcController < ApplicationController
return
end
# Generate new ID token (JWT with pairwise SID, no nonce for refresh grants)
id_token = OidcJwtService.generate_id_token(user, application, consent: consent)
# Generate new ID token (JWT with pairwise SID and at_hash, no nonce for refresh grants)
id_token = OidcJwtService.generate_id_token(
user,
application,
consent: consent,
access_token: new_access_token.plaintext_token
)
# Return new tokens
render json: {

View File

@@ -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)
def generate_id_token(user, application, consent: nil, nonce: nil, access_token: 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,14 @@ class OidcJwtService
# Add nonce if provided (OIDC requires this for implicit flow)
payload[:nonce] = nonce if nonce.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?
sha256 = Digest::SHA256.digest(access_token)
at_hash = Base64.urlsafe_encode64(sha256[0..15], padding: false)
payload[:at_hash] = at_hash
end
# Add groups if user has any
if user.groups.any?
payload[:groups] = user.groups.pluck(:name)