diff --git a/src/IdentityServer/Configuration/DependencyInjection/Options/UserInteractionOptions.cs b/src/IdentityServer/Configuration/DependencyInjection/Options/UserInteractionOptions.cs
index 5bd59a45a..352161ea6 100644
--- a/src/IdentityServer/Configuration/DependencyInjection/Options/UserInteractionOptions.cs
+++ b/src/IdentityServer/Configuration/DependencyInjection/Options/UserInteractionOptions.cs
@@ -5,6 +5,7 @@
#nullable enable
using Duende.IdentityServer.Extensions;
+using Duende.IdentityServer.ResponseHandling;
using System.Collections.Generic;
namespace Duende.IdentityServer.Configuration;
@@ -132,8 +133,13 @@ public class UserInteractionOptions
public bool AllowOriginInReturnUrl { get; set; }
///
- /// The collection of OIDC prompt modes supported and that will be published in discovery.
- /// The value "create" is omitted unless the CreateAccountUrl value is set.
+ /// The collection of OIDC prompt modes supported and that will be published
+ /// in discovery. By default, this includes all values in . If the option is set, then the "create" value is also
+ /// included. If additional prompt values are added, a customized is also required to
+ /// handle those values.
///
public ICollection PromptValuesSupported { get; set; } = new HashSet(Constants.SupportedPromptModes);
}
diff --git a/test/IdentityServer.IntegrationTests/Endpoints/Authorize/AuthorizeTests.cs b/test/IdentityServer.IntegrationTests/Endpoints/Authorize/AuthorizeTests.cs
index 0b7a8f93a..f79c14b97 100644
--- a/test/IdentityServer.IntegrationTests/Endpoints/Authorize/AuthorizeTests.cs
+++ b/test/IdentityServer.IntegrationTests/Endpoints/Authorize/AuthorizeTests.cs
@@ -1485,14 +1485,72 @@ public async Task custom_request_should_have_authorization_params(Type storeType
_mockPipeline.CustomRequest.Parameters.AllKeys.Should().Contain("foo");
_mockPipeline.CustomRequest.Parameters["foo"].Should().Be("bar");
}
+
+ [Fact]
+ public async Task custom_prompt_values_should_raise_error_with_default_interaction_service()
+ {
+ _mockPipeline.Options.UserInteraction.PromptValuesSupported.Add("custom-prompt");
+ await _mockPipeline.LoginAsync("bob");
+
+ var url = _mockPipeline.CreateAuthorizeUrl(
+ clientId: "client1",
+ responseType: "id_token",
+ scope: "openid profile",
+ redirectUri: "https://client1/callback",
+ state: "123_state",
+ nonce: "123_nonce",
+ extra: new { prompt = "custom-prompt" }
+ );
+
+ _mockPipeline.BrowserClient.AllowAutoRedirect = false;
+
+ Func a = () => _mockPipeline.BrowserClient.GetAsync(url);
+ await a.Should().ThrowAsync();
+ }
+
+ [Fact]
+ public async Task custom_prompt_value_should_be_passed_to_custom_interaction_service()
+ {
+ var mockAuthzInteractionService = new MockAuthzInteractionService();
+ mockAuthzInteractionService.Response.RedirectUrl = "/custom";
+ _mockPipeline.OnPostConfigureServices += services =>
+ {
+ services.AddTransient(typeof(IAuthorizeInteractionResponseGenerator), svc => mockAuthzInteractionService);
+ };
+ _mockPipeline.Initialize();
+
+ _mockPipeline.Options.UserInteraction.PromptValuesSupported.Add("custom-prompt");
+
+ await _mockPipeline.LoginAsync("bob");
+
+ var url = _mockPipeline.CreateAuthorizeUrl(
+ clientId: "client1",
+ responseType: "id_token",
+ scope: "openid profile",
+ redirectUri: "https://client1/callback",
+ state: "123_state",
+ nonce: "123_nonce",
+ extra: new { prompt = "custom-prompt" }
+ );
+
+ _mockPipeline.BrowserClient.AllowAutoRedirect = false;
+
+ var response = await _mockPipeline.BrowserClient.GetAsync(url);
+ response.Headers.Location.GetLeftPart(UriPartial.Path).Should().Be("https://server/custom");
+ mockAuthzInteractionService.Request.PromptModes.Should()
+ .Contain("custom-prompt").And
+ .HaveCount(1);
+ }
}
public class MockAuthzInteractionService : IAuthorizeInteractionResponseGenerator
{
public InteractionResponse Response { get; set; } = new InteractionResponse();
+ public ValidatedAuthorizeRequest Request { get; internal set; }
public Task ProcessInteractionAsync(ValidatedAuthorizeRequest request, ConsentResponse consent = null)
{
+ Request = request;
return Task.FromResult(Response);
}
}
\ No newline at end of file