diff --git a/docs/reference/options.rst b/docs/reference/options.rst
index 9dfb8e2ec..c8cacc741 100644
--- a/docs/reference/options.rst
+++ b/docs/reference/options.rst
@@ -21,6 +21,10 @@ IdentityServer Options
* ``StrictJarValidation``
When enabled, applies strict JWT-secured authorization request validation, including typ and content-type checks, according to IETF spec. Defaults to ``false``.
+* ``StrictAuthorizeErrorResponses``
+ When enabled, the authorize endpoint returns every error to the client's redirect URI once the redirect URI and response mode have been validated, as RFC 6749 section 4.1.2.1 describes. Errors found before that still go to the error page: an unknown client or redirect URI, an invalid request object or ``request_uri``, and a missing or unknown ``response_type``.
+ Defaults to ``false``, which returns only ``access_denied``, ``account_selection_required``, ``login_required``, ``consent_required`` and ``interaction_required`` to the client.
+
* ``EnableAuthorizeResponseIssuerParam``
When enabled, the authorize response will include the issuer ``iss`` parameter, and advertises support in the discovery document. Defaults to ``false``.
diff --git a/src/Open.IdentityServer/src/Configuration/DependencyInjection/Options/IdentityServerOptions.cs b/src/Open.IdentityServer/src/Configuration/DependencyInjection/Options/IdentityServerOptions.cs
index 603281e43..7e8e276d7 100644
--- a/src/Open.IdentityServer/src/Configuration/DependencyInjection/Options/IdentityServerOptions.cs
+++ b/src/Open.IdentityServer/src/Configuration/DependencyInjection/Options/IdentityServerOptions.cs
@@ -50,6 +50,11 @@ public class IdentityServerOptions
///
public bool StrictJarValidation { get; set; } = false;
+ ///
+ /// Specifies whether the authorize endpoint returns all errors to the client once the redirect URI and response mode have been validated, as RFC 6749 section 4.1.2.1 describes.
+ ///
+ public bool StrictAuthorizeErrorResponses { get; set; } = false;
+
///
/// Gets or sets the supported JWT request object signing algorithms.
/// Defaults to an empty collection.
diff --git a/src/Open.IdentityServer/src/Endpoints/Results/AuthorizeResult.cs b/src/Open.IdentityServer/src/Endpoints/Results/AuthorizeResult.cs
index 81352b900..27c431abd 100644
--- a/src/Open.IdentityServer/src/Endpoints/Results/AuthorizeResult.cs
+++ b/src/Open.IdentityServer/src/Endpoints/Results/AuthorizeResult.cs
@@ -85,7 +85,9 @@ private async Task ProcessErrorAsync(HttpContext context)
Response.Error == OidcConstants.AuthorizeErrors.ConsentRequired ||
Response.Error == OidcConstants.AuthorizeErrors.InteractionRequired;
- if (isSafeError)
+ var returnToClientInStrictMode = _options.StrictAuthorizeErrorResponses && Response.RedirectUri != null &&
+ Response.Request?.ResponseMode != null;
+ if (isSafeError || returnToClientInStrictMode)
{
// this scenario we can return back to the client
await ProcessResponseAsync(context);
diff --git a/src/Open.IdentityServer/test/Open.IdentityServer.UnitTests/Endpoints/Results/AuthorizeResultTests.cs b/src/Open.IdentityServer/test/Open.IdentityServer.UnitTests/Endpoints/Results/AuthorizeResultTests.cs
index 5d3b62744..97f7ef9e5 100644
--- a/src/Open.IdentityServer/test/Open.IdentityServer.UnitTests/Endpoints/Results/AuthorizeResultTests.cs
+++ b/src/Open.IdentityServer/test/Open.IdentityServer.UnitTests/Endpoints/Results/AuthorizeResultTests.cs
@@ -97,6 +97,69 @@ public async Task SafeErrors_ShouldReturnToClient(string error)
location.Should().Contain("#_");
}
+ [Theory]
+ [InlineData(OidcConstants.AuthorizeErrors.InvalidRequest)]
+ [InlineData(OidcConstants.AuthorizeErrors.TemporarilyUnavailable)]
+ [InlineData(OidcConstants.AuthorizeErrors.UnmetAuthenticationRequirements)]
+ public async Task OtherErrors_WhenStrictAuthorizeErrorResponsesDisabled_ShouldRedirectToErrorPage(string error)
+ {
+ _response.Error = error;
+ _response.Request = new ValidatedAuthorizeRequest
+ {
+ ResponseMode = OidcConstants.ResponseModes.Query,
+ RedirectUri = "http://client/callback"
+ };
+
+ await _subject.ExecuteAsync(_context);
+
+ _mockErrorMessageStore.Messages.Count.Should().Be(1);
+ _context.Response.Headers["Location"].First().Should().StartWith("https://server/error");
+ }
+
+ [Theory]
+ [InlineData(OidcConstants.AuthorizeErrors.InvalidRequest)]
+ [InlineData(OidcConstants.AuthorizeErrors.TemporarilyUnavailable)]
+ [InlineData(OidcConstants.AuthorizeErrors.UnmetAuthenticationRequirements)]
+ [InlineData("some_error")]
+ public async Task OtherErrors_WhenStrictAuthorizeErrorResponsesEnabled_ShouldReturnToClient(string error)
+ {
+ _options.StrictAuthorizeErrorResponses = true;
+ _response.Error = error;
+ _response.Request = new ValidatedAuthorizeRequest
+ {
+ ResponseMode = OidcConstants.ResponseModes.Query,
+ RedirectUri = "http://client/callback"
+ };
+
+ await _subject.ExecuteAsync(_context);
+
+ _mockErrorMessageStore.Messages.Should().BeEmpty();
+ var location = _context.Response.Headers["Location"].First();
+ location.Should().StartWith("http://client/callback");
+ QueryHelpers.ParseQuery(new Uri(location).Query)["error"].First().Should().Be(error);
+ }
+
+ [Theory]
+ [InlineData(OidcConstants.ResponseModes.Query, null)]
+ [InlineData(null, "http://client/callback")]
+ public async Task OtherErrors_WhenStrictAuthorizeErrorResponsesEnabled_AndResponseNotRoutable_ShouldRedirectToErrorPage(
+ string responseMode, string redirectUri)
+ {
+ // the validator sets these only once the redirect uri and response mode are known to be valid
+ _options.StrictAuthorizeErrorResponses = true;
+ _response.Error = OidcConstants.AuthorizeErrors.InvalidRequest;
+ _response.Request = new ValidatedAuthorizeRequest
+ {
+ ResponseMode = responseMode,
+ RedirectUri = redirectUri
+ };
+
+ await _subject.ExecuteAsync(_context);
+
+ _mockErrorMessageStore.Messages.Count.Should().Be(1);
+ _context.Response.Headers["Location"].First().Should().StartWith("https://server/error");
+ }
+
[Theory]
[InlineData(OidcConstants.AuthorizeErrors.AccountSelectionRequired)]
[InlineData(OidcConstants.AuthorizeErrors.LoginRequired)]