From f3b8f7793ad7082a3604c02a750f92ca485ab100 Mon Sep 17 00:00:00 2001 From: joachim-kelsen Date: Wed, 10 Dec 2025 23:21:53 +0100 Subject: [PATCH 1/8] update: client with id token expiration field --- ....Designer.cs => 20251210220900_InitialCreate.Designer.cs} | 5 ++++- ...0911_InitialCreate.cs => 20251210220900_InitialCreate.cs} | 1 + .../Migrations/AuthorizationDbContextModelSnapshot.cs | 3 +++ src/AuthServer/Entities/Client.cs | 5 +++++ 4 files changed, 13 insertions(+), 1 deletion(-) rename src/AuthServer.TestIdentityProvider/Migrations/{20251119210911_InitialCreate.Designer.cs => 20251210220900_InitialCreate.Designer.cs} (99%) rename src/AuthServer.TestIdentityProvider/Migrations/{20251119210911_InitialCreate.cs => 20251210220900_InitialCreate.cs} (99%) diff --git a/src/AuthServer.TestIdentityProvider/Migrations/20251119210911_InitialCreate.Designer.cs b/src/AuthServer.TestIdentityProvider/Migrations/20251210220900_InitialCreate.Designer.cs similarity index 99% rename from src/AuthServer.TestIdentityProvider/Migrations/20251119210911_InitialCreate.Designer.cs rename to src/AuthServer.TestIdentityProvider/Migrations/20251210220900_InitialCreate.Designer.cs index 98d285d7..397d9d05 100644 --- a/src/AuthServer.TestIdentityProvider/Migrations/20251119210911_InitialCreate.Designer.cs +++ b/src/AuthServer.TestIdentityProvider/Migrations/20251210220900_InitialCreate.Designer.cs @@ -12,7 +12,7 @@ namespace AuthServer.TestIdentityProvider.Migrations { [DbContext(typeof(AuthorizationDbContext))] - [Migration("20251119210911_InitialCreate")] + [Migration("20251210220900_InitialCreate")] partial class InitialCreate { /// @@ -454,6 +454,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("IdTokenEncryptedResponseEnc") .HasColumnType("int"); + b.Property("IdTokenExpiration") + .HasColumnType("int"); + b.Property("IdTokenSignedResponseAlg") .HasColumnType("int"); diff --git a/src/AuthServer.TestIdentityProvider/Migrations/20251119210911_InitialCreate.cs b/src/AuthServer.TestIdentityProvider/Migrations/20251210220900_InitialCreate.cs similarity index 99% rename from src/AuthServer.TestIdentityProvider/Migrations/20251119210911_InitialCreate.cs rename to src/AuthServer.TestIdentityProvider/Migrations/20251210220900_InitialCreate.cs index a4d3573d..2ae2a373 100644 --- a/src/AuthServer.TestIdentityProvider/Migrations/20251119210911_InitialCreate.cs +++ b/src/AuthServer.TestIdentityProvider/Migrations/20251210220900_InitialCreate.cs @@ -128,6 +128,7 @@ protected override void Up(MigrationBuilder migrationBuilder) SecretExpiration = table.Column(type: "int", nullable: true), AccessTokenExpiration = table.Column(type: "int", nullable: false), RefreshTokenExpiration = table.Column(type: "int", nullable: true), + IdTokenExpiration = table.Column(type: "int", nullable: true), AuthorizationCodeExpiration = table.Column(type: "int", nullable: true), DeviceCodeExpiration = table.Column(type: "int", nullable: true), RequestUriExpiration = table.Column(type: "int", nullable: true), diff --git a/src/AuthServer.TestIdentityProvider/Migrations/AuthorizationDbContextModelSnapshot.cs b/src/AuthServer.TestIdentityProvider/Migrations/AuthorizationDbContextModelSnapshot.cs index 53fb1dbd..b5f921ec 100644 --- a/src/AuthServer.TestIdentityProvider/Migrations/AuthorizationDbContextModelSnapshot.cs +++ b/src/AuthServer.TestIdentityProvider/Migrations/AuthorizationDbContextModelSnapshot.cs @@ -451,6 +451,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IdTokenEncryptedResponseEnc") .HasColumnType("int"); + b.Property("IdTokenExpiration") + .HasColumnType("int"); + b.Property("IdTokenSignedResponseAlg") .HasColumnType("int"); diff --git a/src/AuthServer/Entities/Client.cs b/src/AuthServer/Entities/Client.cs index cdfe4557..d25849ed 100644 --- a/src/AuthServer/Entities/Client.cs +++ b/src/AuthServer/Entities/Client.cs @@ -55,6 +55,11 @@ private Client() { } /// Refresh token lifetime in seconds. /// public int? RefreshTokenExpiration { get; set; } + + /// + /// Id token lifetime in seconds. + /// + public int? IdTokenExpiration { get; set; } /// /// Authorization code lifetime in seconds. From 35571876e4cabf4b998c25f1bcc1bce76879e769 Mon Sep 17 00:00:00 2001 From: joachim-kelsen Date: Wed, 10 Dec 2025 23:22:06 +0100 Subject: [PATCH 2/8] remove: unused package --- src/AuthServer/AuthServer.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/AuthServer/AuthServer.csproj b/src/AuthServer/AuthServer.csproj index f9a182a0..2d0c69ff 100644 --- a/src/AuthServer/AuthServer.csproj +++ b/src/AuthServer/AuthServer.csproj @@ -28,7 +28,6 @@ - From cd80615ae5f3be5fe0b7728d074abcbb9c87285a Mon Sep 17 00:00:00 2001 From: joachim-kelsen Date: Wed, 10 Dec 2025 23:23:10 +0100 Subject: [PATCH 3/8] update: accept id token expiration register parameter --- src/AuthServer/Core/Parameter.cs | 1 + src/AuthServer/Register/RegisterRequest.cs | 1 + src/AuthServer/Register/RegisterRequestAccessor.cs | 2 ++ .../Register/RegisterRequestAccessorTest.cs | 2 ++ 4 files changed, 6 insertions(+) diff --git a/src/AuthServer/Core/Parameter.cs b/src/AuthServer/Core/Parameter.cs index 39f029ce..e32d2774 100644 --- a/src/AuthServer/Core/Parameter.cs +++ b/src/AuthServer/Core/Parameter.cs @@ -121,6 +121,7 @@ public static class Parameter public const string DeviceCodeExpiration = "device_code_expiration"; public const string AccessTokenExpiration = "access_token_expiration"; public const string RefreshTokenExpiration = "refresh_token_expiration"; + public const string IdTokenExpiration = "id_token_expiration"; public const string ClientSecretExpiration = "client_secret_expiration"; public const string JwksExpiration = "jwks_expiration"; public const string RequestUriExpiration = "request_uri_expiration"; diff --git a/src/AuthServer/Register/RegisterRequest.cs b/src/AuthServer/Register/RegisterRequest.cs index 51bceb10..6dec3fcc 100644 --- a/src/AuthServer/Register/RegisterRequest.cs +++ b/src/AuthServer/Register/RegisterRequest.cs @@ -40,6 +40,7 @@ internal class RegisterRequest public int? DeviceCodeExpiration { get; init; } public int? AccessTokenExpiration { get; init; } public int? RefreshTokenExpiration { get; init; } + public int? IdTokenExpiration { get; init; } public int? ClientSecretExpiration { get; init; } public int? JwksExpiration { get; init; } public int? RequestUriExpiration { get; init; } diff --git a/src/AuthServer/Register/RegisterRequestAccessor.cs b/src/AuthServer/Register/RegisterRequestAccessor.cs index 7d92ee56..16fec466 100644 --- a/src/AuthServer/Register/RegisterRequestAccessor.cs +++ b/src/AuthServer/Register/RegisterRequestAccessor.cs @@ -74,6 +74,7 @@ public async Task GetRequest(HttpRequest httpRequest) var deviceCodeExpiration = json.GetIntValue(Parameter.DeviceCodeExpiration); var accessTokenExpiration = json.GetIntValue(Parameter.AccessTokenExpiration); var refreshTokenExpiration = json.GetIntValue(Parameter.RefreshTokenExpiration); + var idTokenExpiration = json.GetIntValue(Parameter.IdTokenExpiration); var clientSecretExpiration = json.GetIntValue(Parameter.ClientSecretExpiration); var jwksExpiration = json.GetIntValue(Parameter.JwksExpiration); var requestUriExpiration = json.GetIntValue(Parameter.RequestUriExpiration); @@ -129,6 +130,7 @@ public async Task GetRequest(HttpRequest httpRequest) DeviceCodeExpiration = deviceCodeExpiration, AccessTokenExpiration = accessTokenExpiration, RefreshTokenExpiration = refreshTokenExpiration, + IdTokenExpiration = idTokenExpiration, ClientSecretExpiration = clientSecretExpiration, JwksExpiration = jwksExpiration, RequestUriExpiration = requestUriExpiration, diff --git a/tests/AuthServer.Tests.UnitTest/Register/RegisterRequestAccessorTest.cs b/tests/AuthServer.Tests.UnitTest/Register/RegisterRequestAccessorTest.cs index 2322b26a..4d38df8b 100644 --- a/tests/AuthServer.Tests.UnitTest/Register/RegisterRequestAccessorTest.cs +++ b/tests/AuthServer.Tests.UnitTest/Register/RegisterRequestAccessorTest.cs @@ -285,6 +285,7 @@ public async Task GetRequest_IntParametersPostAndPut_ExpectValues(string method, { Parameter.DeviceCodeExpiration, value }, { Parameter.AccessTokenExpiration, value }, { Parameter.RefreshTokenExpiration, value }, + { Parameter.IdTokenExpiration, value }, { Parameter.ClientSecretExpiration, value }, { Parameter.JwksExpiration, value }, { Parameter.RequestUriExpiration, value }, @@ -333,6 +334,7 @@ public async Task GetRequest_IntParametersPostAndPut_ExpectValues(string method, Assert.Equal(expectedValue, request.DeviceCodeExpiration); Assert.Equal(expectedValue, request.AccessTokenExpiration); Assert.Equal(expectedValue, request.RefreshTokenExpiration); + Assert.Equal(expectedValue, request.IdTokenExpiration); Assert.Equal(expectedValue, request.ClientSecretExpiration); Assert.Equal(expectedValue, request.JwksExpiration); Assert.Equal(expectedValue, request.RequestUriExpiration); From 686ca5abaa983d8e49ee0f0f83a983e208d1b018 Mon Sep 17 00:00:00 2001 From: joachim-kelsen Date: Wed, 10 Dec 2025 23:23:40 +0100 Subject: [PATCH 4/8] update: validate id token expiration register parameter --- src/AuthServer/Register/RegisterError.cs | 3 ++ .../Register/RegisterRequestValidator.cs | 36 +++++++++++++++++++ .../Register/RegisterValidatedRequest.cs | 1 + .../Register/RegisterRequestValidatorTest.cs | 25 +++++++++++++ 4 files changed, 65 insertions(+) diff --git a/src/AuthServer/Register/RegisterError.cs b/src/AuthServer/Register/RegisterError.cs index e8141c20..a36ba5a7 100644 --- a/src/AuthServer/Register/RegisterError.cs +++ b/src/AuthServer/Register/RegisterError.cs @@ -94,6 +94,9 @@ internal static class RegisterError public static readonly ProcessError InvalidRefreshTokenExpiration = new(ErrorCode.InvalidClientMetadata, "invalid refresh_token_expiration", ResultCode.BadRequest); + public static ProcessError InvalidIdTokenExpiration = + new(ErrorCode.InvalidClientMetadata, "invalid id_token_expiration", ResultCode.BadRequest); + public static readonly ProcessError InvalidClientSecretExpiration = new(ErrorCode.InvalidClientMetadata, "invalid client_secret_expiration", ResultCode.BadRequest); diff --git a/src/AuthServer/Register/RegisterRequestValidator.cs b/src/AuthServer/Register/RegisterRequestValidator.cs index 58cae849..b98b9313 100644 --- a/src/AuthServer/Register/RegisterRequestValidator.cs +++ b/src/AuthServer/Register/RegisterRequestValidator.cs @@ -159,6 +159,12 @@ public async Task> Validat return refreshTokenExpirationError; } + var idTokenExpirationError = ValidateIdTokenExpiration(request, validatedRequest); + if (idTokenExpirationError is not null) + { + return idTokenExpirationError; + } + var clientSecretExpirationError = ValidateClientSecretExpiration(request, validatedRequest); if (clientSecretExpirationError is not null) { @@ -994,6 +1000,36 @@ select ampersandPosition > 0 return null; } + /// + /// IdTokenExpiration is OPTIONAL. + /// Default value is 3600. + /// + /// + /// + /// + private static ProcessError? ValidateIdTokenExpiration(RegisterRequest request, + RegisterValidatedRequest validatedRequest) + { + if (request.IdTokenExpiration is null) + { + validatedRequest.IdTokenExpiration = + validatedRequest.GrantTypes.IsIntersected(GrantTypeConstants.OpenIdConnectInitiatingGrantTypes) + ? 3600 // defaulted to 1 hour + : null; + + return null; + } + + // between 60 seconds and 1 day + if (request.IdTokenExpiration is < 60 or > 86400) + { + return RegisterError.InvalidIdTokenExpiration; + } + + validatedRequest.IdTokenExpiration = request.IdTokenExpiration; + return null; + } + /// /// ClientSecretExpiration is OPTIONAL. /// diff --git a/src/AuthServer/Register/RegisterValidatedRequest.cs b/src/AuthServer/Register/RegisterValidatedRequest.cs index e85ed825..8d39a668 100644 --- a/src/AuthServer/Register/RegisterValidatedRequest.cs +++ b/src/AuthServer/Register/RegisterValidatedRequest.cs @@ -42,6 +42,7 @@ internal class RegisterValidatedRequest public int? DeviceCodeExpiration { get; set; } public int AccessTokenExpiration { get; set; } public int? RefreshTokenExpiration { get; set; } + public int? IdTokenExpiration { get; set; } public int? ClientSecretExpiration { get; set; } public int? JwksExpiration { get; set; } public int? RequestUriExpiration { get; set; } diff --git a/tests/AuthServer.Tests.UnitTest/Register/RegisterRequestValidatorTest.cs b/tests/AuthServer.Tests.UnitTest/Register/RegisterRequestValidatorTest.cs index 32d670b5..2fac2b6c 100644 --- a/tests/AuthServer.Tests.UnitTest/Register/RegisterRequestValidatorTest.cs +++ b/tests/AuthServer.Tests.UnitTest/Register/RegisterRequestValidatorTest.cs @@ -966,6 +966,31 @@ public async Task Validate_InvalidRefreshTokenExpiration_ExpectInvalidRefreshTok Assert.Equal(RegisterError.InvalidRefreshTokenExpiration, processResult); } + [Theory] + [InlineData(59)] + [InlineData(86401)] + public async Task Validate_InvalidIdTokenExpiration_ExpectInvalidIdTokenExpiration(int expiration) + { + // Arrange + var serviceProvider = BuildServiceProvider(); + var validator = serviceProvider + .GetRequiredService>(); + + var request = new RegisterRequest + { + Method = HttpMethod.Post, + ClientName = "web-app", + RedirectUris = ["https://webapp.authserver.dk/callback"], + IdTokenExpiration = expiration + }; + + // Act + var processResult = await validator.Validate(request, CancellationToken.None); + + // Assert + Assert.Equal(RegisterError.InvalidIdTokenExpiration, processResult); + } + [Fact] public async Task Validate_InvalidClientSecretExpiration_ExpectInvalidClientSecretExpiration() { From d8d2c9d7c3a3c16959a04cfd32c221c4fffa703c Mon Sep 17 00:00:00 2001 From: joachim-kelsen Date: Wed, 10 Dec 2025 23:24:00 +0100 Subject: [PATCH 5/8] update: process id token expiration register parameter --- src/AuthServer/Register/GetRegisterResponse.cs | 4 ++++ src/AuthServer/Register/RegisterEndpointHandler.cs | 1 + src/AuthServer/Register/RegisterRequestProcessor.cs | 2 ++ src/AuthServer/Register/RegisterResponse.cs | 1 + .../Register/RegisterRequestProcessorTest.cs | 4 ++++ 5 files changed, 12 insertions(+) diff --git a/src/AuthServer/Register/GetRegisterResponse.cs b/src/AuthServer/Register/GetRegisterResponse.cs index cad65909..39327bee 100644 --- a/src/AuthServer/Register/GetRegisterResponse.cs +++ b/src/AuthServer/Register/GetRegisterResponse.cs @@ -150,6 +150,10 @@ internal class GetRegisterResponse [JsonPropertyName(Parameter.RefreshTokenExpiration)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? RefreshTokenExpiration { get; init; } + + [JsonPropertyName(Parameter.IdTokenExpiration)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? IdTokenExpiration { get; init; } [JsonPropertyName(Parameter.ClientSecretExpiration)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] diff --git a/src/AuthServer/Register/RegisterEndpointHandler.cs b/src/AuthServer/Register/RegisterEndpointHandler.cs index b6cf6348..637a2ff1 100644 --- a/src/AuthServer/Register/RegisterEndpointHandler.cs +++ b/src/AuthServer/Register/RegisterEndpointHandler.cs @@ -65,6 +65,7 @@ public async Task Handle(HttpContext httpContext, CancellationToken can DeviceCodeExpiration = client.DeviceCodeExpiration, AccessTokenExpiration = client.AccessTokenExpiration, RefreshTokenExpiration = client.RefreshTokenExpiration, + IdTokenExpiration = client.IdTokenExpiration, ClientSecretExpiration = client.ClientSecretExpiration, JwksExpiration = client.JwksExpiration, RequestUriExpiration = client.RequestUriExpiration, diff --git a/src/AuthServer/Register/RegisterRequestProcessor.cs b/src/AuthServer/Register/RegisterRequestProcessor.cs index ed80c860..5765d188 100644 --- a/src/AuthServer/Register/RegisterRequestProcessor.cs +++ b/src/AuthServer/Register/RegisterRequestProcessor.cs @@ -139,6 +139,7 @@ public async Task> Process(RegisterValidat DeviceCodeExpiration = client.DeviceCodeExpiration, AccessTokenExpiration = client.AccessTokenExpiration, RefreshTokenExpiration = client.RefreshTokenExpiration, + IdTokenExpiration = client.IdTokenExpiration, ClientSecretExpiration = client.SecretExpiration, JwksExpiration = client.JwksExpiration, RequestUriExpiration = client.RequestUriExpiration, @@ -212,6 +213,7 @@ private static void SetValues(RegisterValidatedRequest request, Client client) client.DeviceCodeExpiration = request.DeviceCodeExpiration; client.AccessTokenExpiration = request.AccessTokenExpiration; client.RefreshTokenExpiration = request.RefreshTokenExpiration; + client.IdTokenExpiration = request.IdTokenExpiration; client.SecretExpiration = request.ClientSecretExpiration; client.JwksExpiration = request.JwksExpiration; client.RequestUriExpiration = request.RequestUriExpiration; diff --git a/src/AuthServer/Register/RegisterResponse.cs b/src/AuthServer/Register/RegisterResponse.cs index 0dcd98cd..763a4238 100644 --- a/src/AuthServer/Register/RegisterResponse.cs +++ b/src/AuthServer/Register/RegisterResponse.cs @@ -44,6 +44,7 @@ internal class RegisterResponse public required int? DeviceCodeExpiration { get; init; } public required int AccessTokenExpiration { get; init; } public required int? RefreshTokenExpiration { get; init; } + public required int? IdTokenExpiration { get; init; } public required int? ClientSecretExpiration { get; init; } public required int? JwksExpiration { get; init; } public required int? RequestUriExpiration { get; init; } diff --git a/tests/AuthServer.Tests.UnitTest/Register/RegisterRequestProcessorTest.cs b/tests/AuthServer.Tests.UnitTest/Register/RegisterRequestProcessorTest.cs index 13892b95..f6c4358b 100644 --- a/tests/AuthServer.Tests.UnitTest/Register/RegisterRequestProcessorTest.cs +++ b/tests/AuthServer.Tests.UnitTest/Register/RegisterRequestProcessorTest.cs @@ -57,6 +57,7 @@ public async Task Process_PostRegister_ExpectRegisterResponse() DefaultMaxAge = 600, InitiateLoginUri = "https://webapp.authserver.dk/remote-login", RefreshTokenExpiration = 86400, + IdTokenExpiration = 3600, RequireReferenceToken = false, RequireSignedRequestObject = true, RequirePushedAuthorizationRequests = true, @@ -140,6 +141,7 @@ public async Task Process_PostRegister_ExpectRegisterResponse() Assert.Equal(request.DeviceCodeExpiration, response.DeviceCodeExpiration); Assert.Equal(request.AccessTokenExpiration, response.AccessTokenExpiration); Assert.Equal(request.RefreshTokenExpiration, response.RefreshTokenExpiration); + Assert.Equal(request.IdTokenExpiration, response.IdTokenExpiration); Assert.Equal(request.JwksExpiration, response.JwksExpiration); Assert.Equal(request.RequestUriExpiration, response.RequestUriExpiration); Assert.Equal(request.DPoPNonceExpiration, response.DPoPNonceExpiration); @@ -272,6 +274,7 @@ public async Task Process_GetRegister_ExpectRegisterResponse() Assert.Equal(client.DeviceCodeExpiration, response.DeviceCodeExpiration); Assert.Equal(client.AccessTokenExpiration, response.AccessTokenExpiration); Assert.Equal(client.RefreshTokenExpiration, response.RefreshTokenExpiration); + Assert.Equal(client.IdTokenExpiration, response.IdTokenExpiration); Assert.Equal(client.DPoPNonceExpiration, response.DPoPNonceExpiration); Assert.Equal(client.JwksExpiration, response.JwksExpiration); Assert.Equal(client.RequestUriExpiration, response.RequestUriExpiration); @@ -341,6 +344,7 @@ private async Task GetClient() DeviceCodeExpiration = 300, AccessTokenExpiration = 600, RefreshTokenExpiration = 86400, + IdTokenExpiration = 3600, SecretExpiration = 86400 * 30, JwksExpiration = 86400 * 30, RequestUriExpiration = 60, From 04701a41df14112901165b9e7487e727a563dd70 Mon Sep 17 00:00:00 2001 From: joachim-kelsen Date: Wed, 10 Dec 2025 23:32:29 +0100 Subject: [PATCH 6/8] update: query client with id token expiration field --- src/AuthServer/Cache/CachedClientStore.cs | 1 + src/AuthServer/Cache/Entities/CachedClient.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/AuthServer/Cache/CachedClientStore.cs b/src/AuthServer/Cache/CachedClientStore.cs index cf43ae35..1f2d3dbc 100644 --- a/src/AuthServer/Cache/CachedClientStore.cs +++ b/src/AuthServer/Cache/CachedClientStore.cs @@ -87,6 +87,7 @@ public async Task Delete(string entityId, CancellationToken cancellationToken) SecretHash = client.SecretHash, SecretExpiresAt = client.SecretExpiresAt, AccessTokenExpiration = client.AccessTokenExpiration, + IdTokenExpiration = client.IdTokenExpiration, DeviceCodeExpiration = client.DeviceCodeExpiration, ClientUri = client.ClientUri, LogoUri = client.LogoUri, diff --git a/src/AuthServer/Cache/Entities/CachedClient.cs b/src/AuthServer/Cache/Entities/CachedClient.cs index c6487350..da26fc34 100644 --- a/src/AuthServer/Cache/Entities/CachedClient.cs +++ b/src/AuthServer/Cache/Entities/CachedClient.cs @@ -8,6 +8,7 @@ internal class CachedClient public required string? SecretHash { get; init; } public required DateTime? SecretExpiresAt { get; init; } public required int AccessTokenExpiration { get; init; } + public required int? IdTokenExpiration { get; init; } public required int? DeviceCodeExpiration { get; init; } public required string? ClientUri { get; init; } public required string? LogoUri { get; init; } From 5dd62b458fea5796815726c4f844554835f19ae0 Mon Sep 17 00:00:00 2001 From: joachim-kelsen Date: Wed, 10 Dec 2025 23:37:29 +0100 Subject: [PATCH 7/8] update: set id token expiration from clients id token expiration --- src/AuthServer/TokenBuilders/IdTokenBuilder.cs | 4 +++- .../TokenExchangeGrant/TokenExchangeRequestProcessor.cs | 2 +- .../TokenBuilders/IdTokenBuilderTest.cs | 4 +++- .../TokenExchangeGrant/TokenExchangeRequestProcessorTest.cs | 5 +++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/AuthServer/TokenBuilders/IdTokenBuilder.cs b/src/AuthServer/TokenBuilders/IdTokenBuilder.cs index d8726ba2..49062fa3 100644 --- a/src/AuthServer/TokenBuilders/IdTokenBuilder.cs +++ b/src/AuthServer/TokenBuilders/IdTokenBuilder.cs @@ -61,6 +61,7 @@ public async Task BuildToken(IdTokenArguments arguments, CancellationTok ClientId = x.Client.Id, RequireConsent = x.Client.RequireConsent, RequireIdTokenClaims = x.Client.RequireIdTokenClaims, + IdTokenExpiration = x.Client.IdTokenExpiration!.Value, SessionId = x.Session.Id, SubjectIdentifier = x.Session.SubjectIdentifier.Id, GrantSubject = x.Subject, @@ -113,7 +114,7 @@ public async Task BuildToken(IdTokenArguments arguments, CancellationTok var tokenDescriptor = new SecurityTokenDescriptor { IssuedAt = now, - Expires = now.AddHours(1), + Expires = now.AddSeconds(query.IdTokenExpiration), NotBefore = now, Issuer = _discoveryDocumentOptions.Value.Issuer, SigningCredentials = signingCredentials, @@ -179,6 +180,7 @@ private sealed class IdTokenBuildEntity public required string ClientId { get; init; } public required bool RequireConsent { get; init; } public required bool RequireIdTokenClaims { get; init; } + public required int IdTokenExpiration { get; init; } public required string SessionId { get; init; } public required string SubjectIdentifier { get; init; } public required string GrantSubject { get; init; } diff --git a/src/AuthServer/TokenByGrant/TokenExchangeGrant/TokenExchangeRequestProcessor.cs b/src/AuthServer/TokenByGrant/TokenExchangeGrant/TokenExchangeRequestProcessor.cs index dbf95bd6..4fe6eaaf 100644 --- a/src/AuthServer/TokenByGrant/TokenExchangeGrant/TokenExchangeRequestProcessor.cs +++ b/src/AuthServer/TokenByGrant/TokenExchangeGrant/TokenExchangeRequestProcessor.cs @@ -69,7 +69,7 @@ public async Task Process(TokenExchangeValidatedRequest request, var cachedClient = await _cachedClientStore.Get(request.SubjectToken.ClientId, cancellationToken); var expiresIn = request.RequestedTokenType == TokenTypeIdentifier.IdToken - ? 3600 + ? cachedClient.IdTokenExpiration!.Value : cachedClient.AccessTokenExpiration; var tokenType = request.Jkt is null diff --git a/tests/AuthServer.Tests.UnitTest/TokenBuilders/IdTokenBuilderTest.cs b/tests/AuthServer.Tests.UnitTest/TokenBuilders/IdTokenBuilderTest.cs index 43325082..c8f24cbd 100644 --- a/tests/AuthServer.Tests.UnitTest/TokenBuilders/IdTokenBuilderTest.cs +++ b/tests/AuthServer.Tests.UnitTest/TokenBuilders/IdTokenBuilderTest.cs @@ -169,7 +169,8 @@ private async Task GetAuthorizationGrant(SigningAlg signingA { IdTokenSignedResponseAlg = signingAlg, SubjectType = SubjectType.Pairwise, - RequireIdTokenClaims = true + RequireIdTokenClaims = true, + IdTokenExpiration = 3600 }; client.Scopes.Add(openIdScope); @@ -221,6 +222,7 @@ private async Task GetAuthorizationGrant(SigningAlg signingA IdTokenSignedResponseAlg = signingAlg, IdTokenEncryptedResponseAlg = encryptionAlg, IdTokenEncryptedResponseEnc = encryptionEnc, + IdTokenExpiration = 3600, SubjectType = SubjectType.Pairwise, Jwks = clientJwks, RequireIdTokenClaims = false, diff --git a/tests/AuthServer.Tests.UnitTest/TokenByGrant/TokenExchangeGrant/TokenExchangeRequestProcessorTest.cs b/tests/AuthServer.Tests.UnitTest/TokenByGrant/TokenExchangeGrant/TokenExchangeRequestProcessorTest.cs index 7f1cbdcd..199f352c 100644 --- a/tests/AuthServer.Tests.UnitTest/TokenByGrant/TokenExchangeGrant/TokenExchangeRequestProcessorTest.cs +++ b/tests/AuthServer.Tests.UnitTest/TokenByGrant/TokenExchangeGrant/TokenExchangeRequestProcessorTest.cs @@ -237,7 +237,7 @@ public async Task Process_RequestImpersonatedIdToken_ExpectTokenResponse() // Assert Assert.NotNull(tokenResponse.AccessToken); - Assert.Equal(3600, tokenResponse.ExpiresIn); + Assert.Equal(subjectTokenGrant.Client.IdTokenExpiration!.Value, tokenResponse.ExpiresIn); Assert.Equal(TokenTypeSchemaConstants.Bearer, tokenResponse.TokenType); Assert.Equal(validatedRequest.RequestedTokenType, tokenResponse.IssuedTokenType); Assert.Equal(subjectTokenGrant.Id, tokenResponse.GrantId); @@ -315,7 +315,8 @@ private async Task GetSubjectTokenAuthorizationCodeGrant var session = new Session(subjectIdentifier); var client = new Client("subject-web-app", ApplicationType.Web, TokenEndpointAuthMethod.ClientSecretBasic, 300, 60) { - IdTokenSignedResponseAlg = SigningAlg.RsaSha256 + IdTokenSignedResponseAlg = SigningAlg.RsaSha256, + IdTokenExpiration = 3600 }; var levelOfAssurance = await GetAuthenticationContextReference(LevelOfAssuranceLow); var authorizationCodeGrant = new AuthorizationCodeGrant(session, client, subjectIdentifier.Id, levelOfAssurance); From b3d05cd07788124a5739d7d2db8a94351498eff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joachim=20K=C3=B8cher=20Kelsen?= <71125597+jokk-itu@users.noreply.github.com> Date: Thu, 11 Dec 2025 20:03:26 +0100 Subject: [PATCH 8/8] update: mark field as readonly --- src/AuthServer/Register/RegisterError.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AuthServer/Register/RegisterError.cs b/src/AuthServer/Register/RegisterError.cs index a36ba5a7..b7bb3404 100644 --- a/src/AuthServer/Register/RegisterError.cs +++ b/src/AuthServer/Register/RegisterError.cs @@ -94,7 +94,7 @@ internal static class RegisterError public static readonly ProcessError InvalidRefreshTokenExpiration = new(ErrorCode.InvalidClientMetadata, "invalid refresh_token_expiration", ResultCode.BadRequest); - public static ProcessError InvalidIdTokenExpiration = + public static readonly ProcessError InvalidIdTokenExpiration = new(ErrorCode.InvalidClientMetadata, "invalid id_token_expiration", ResultCode.BadRequest); public static readonly ProcessError InvalidClientSecretExpiration =