diff --git a/src/IdentityServer/IdentityServerTools.cs b/src/IdentityServer/IdentityServerTools.cs index 15d69edeb..c9ca8dc68 100644 --- a/src/IdentityServer/IdentityServerTools.cs +++ b/src/IdentityServer/IdentityServerTools.cs @@ -19,7 +19,7 @@ namespace Duende.IdentityServer; /// public class IdentityServerTools { - internal readonly IServiceProvider ServiceProvider; + internal readonly IServiceProvider ServiceProvider; // TODO - consider removing this, as it is not used. internal readonly IIssuerNameService IssuerNameService; private readonly ITokenCreationService _tokenCreation; private readonly ISystemClock _clock; diff --git a/src/IdentityServer/Services/Default/DefaultBackChannelLogoutService.cs b/src/IdentityServer/Services/Default/DefaultBackChannelLogoutService.cs index 241786d79..54b8d5e94 100644 --- a/src/IdentityServer/Services/Default/DefaultBackChannelLogoutService.cs +++ b/src/IdentityServer/Services/Default/DefaultBackChannelLogoutService.cs @@ -30,7 +30,7 @@ public class DefaultBackChannelLogoutService : IBackChannelLogoutService protected ISystemClock Clock { get; } /// - /// The IdentityServerTools used to create and the JWT. + /// The IdentityServerTools used to create the JWT. /// protected IdentityServerTools Tools { get; } @@ -49,6 +49,11 @@ public class DefaultBackChannelLogoutService : IBackChannelLogoutService /// protected ILogger Logger { get; } + /// + /// Ths issuer name service. + /// + protected IIssuerNameService IssuerNameService { get; } + /// /// Constructor. /// @@ -56,12 +61,14 @@ public class DefaultBackChannelLogoutService : IBackChannelLogoutService /// /// /// + /// /// public DefaultBackChannelLogoutService( ISystemClock clock, IdentityServerTools tools, ILogoutNotificationService logoutNotificationService, IBackChannelLogoutHttpClient backChannelLogoutHttpClient, + IIssuerNameService issuerNameService, ILogger logger) { Clock = clock; @@ -69,6 +76,7 @@ public DefaultBackChannelLogoutService( LogoutNotificationService = logoutNotificationService; HttpClient = backChannelLogoutHttpClient; Logger = logger; + IssuerNameService = issuerNameService; } /// @@ -150,7 +158,8 @@ protected virtual async Task CreateTokenAsync(BackChannelLogoutRequest r return await Tools.IssueJwtAsync(DefaultLogoutTokenLifetime, request.Issuer, IdentityServerConstants.TokenTypes.LogoutToken, claims); } - return await Tools.IssueJwtAsync(DefaultLogoutTokenLifetime, IdentityServerConstants.TokenTypes.LogoutToken, claims); + var issuer = await IssuerNameService.GetCurrentAsync(); + return await Tools.IssueJwtAsync(DefaultLogoutTokenLifetime, issuer, IdentityServerConstants.TokenTypes.LogoutToken, claims); } /// diff --git a/test/IdentityServer.UnitTests/Services/Default/DefaultBackChannelLogoutServiceTests.cs b/test/IdentityServer.UnitTests/Services/Default/DefaultBackChannelLogoutServiceTests.cs new file mode 100644 index 000000000..a864ce618 --- /dev/null +++ b/test/IdentityServer.UnitTests/Services/Default/DefaultBackChannelLogoutServiceTests.cs @@ -0,0 +1,76 @@ +// Copyright (c) Duende Software. All rights reserved. +// See LICENSE in the project root for license information. + + +using System.Collections.Generic; +using System.Text.Json; +using System.Threading.Tasks; +using Duende.IdentityServer; +using Duende.IdentityServer.Configuration; +using Duende.IdentityServer.Services; +using FluentAssertions; +using IdentityModel; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Tokens; +using UnitTests.Common; +using UnitTests.Services.Default.KeyManagement; +using UnitTests.Validation.Setup; +using Xunit; + +namespace UnitTests.Services.Default; + +public class DefaultBackChannelLogoutServiceTests +{ + private class ServiceTestHarness : DefaultBackChannelLogoutService + { + public ServiceTestHarness( + ISystemClock clock, + IdentityServerTools tools, + ILogoutNotificationService logoutNotificationService, + IBackChannelLogoutHttpClient backChannelLogoutHttpClient, + IIssuerNameService issuerNameService, + ILogger logger) + : base(clock, tools, logoutNotificationService, backChannelLogoutHttpClient, issuerNameService, logger) + { + } + + + // CreateTokenAsync is protected, so we use this wrapper to exercise it in our tests + public async Task ExerciseCreateTokenAsync(BackChannelLogoutRequest request) + { + return await CreateTokenAsync(request); + } + } + + [Fact] + public async Task CreateTokenAsync_Should_Set_Issuer_Correctly() + { + var expected = "https://identity.example.com"; + + var mockKeyMaterialService = new MockKeyMaterialService(); + var signingKey = new SigningCredentials(CryptoHelper.CreateRsaSecurityKey(), CryptoHelper.GetRsaSigningAlgorithmValue(IdentityServerConstants.RsaSigningAlgorithm.RS256)); + mockKeyMaterialService.SigningCredentials.Add(signingKey); + + var tokenCreation = new DefaultTokenCreationService(new MockClock(), mockKeyMaterialService, TestIdentityServerOptions.Create(), TestLogger.Create()); + + var issuerNameService = new TestIssuerNameService(expected); + var tools = new IdentityServerTools( + null, // service provider is unused + issuerNameService, + tokenCreation, + new MockClock() + ); + + var subject = new ServiceTestHarness(null, tools, null, null, issuerNameService, null); + var rawToken = await subject.ExerciseCreateTokenAsync(new BackChannelLogoutRequest + { + ClientId = "test_client", + SubjectId = "test_sub", + }); + + + var payload = JsonSerializer.Deserialize>(Base64Url.Decode(rawToken.Split('.')[1])); + payload["iss"].GetString().Should().Be(expected); + } +} \ No newline at end of file