diff --git a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs
index f747354e7b..ae7e866fb2 100644
--- a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs
+++ b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs
@@ -323,6 +323,41 @@ namespace Microsoft.AspNet.Authentication.Cookies
protected override void ApplyResponseChallenge()
{
+ if (ShouldConvertChallengeToForbidden())
+ {
+ // Handle 403 by redirecting to AccessDeniedPath if set
+ if (Options.AccessDeniedPath.HasValue)
+ {
+ try
+ {
+ var accessDeniedUri =
+ Request.Scheme +
+ "://" +
+ Request.Host +
+ Request.PathBase +
+ Options.AccessDeniedPath;
+
+ var redirectContext = new CookieApplyRedirectContext(Context, Options, accessDeniedUri);
+ Options.Notifications.ApplyRedirect(redirectContext);
+ }
+ catch (Exception exception)
+ {
+ var exceptionContext = new CookieExceptionContext(Context, Options,
+ CookieExceptionContext.ExceptionLocation.ApplyResponseChallenge, exception, ticket: null);
+ Options.Notifications.Exception(exceptionContext);
+ if (exceptionContext.Rethrow)
+ {
+ throw;
+ }
+ }
+ }
+ else
+ {
+ Response.StatusCode = 403;
+ }
+ return;
+ }
+
if (Response.StatusCode != 401 || !Options.LoginPath.HasValue )
{
return;
diff --git a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationMiddleware.cs
index 21df592538..e3ef3f5700 100644
--- a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationMiddleware.cs
+++ b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationMiddleware.cs
@@ -4,8 +4,6 @@
using System;
using Microsoft.AspNet.Authentication.Cookies.Infrastructure;
using Microsoft.AspNet.Authentication.DataHandler;
-using Microsoft.AspNet.Authentication.Cookies.Infrastructure;
-using Microsoft.AspNet.Authentication.DataHandler;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.DataProtection;
using Microsoft.Framework.Logging;
diff --git a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationOptions.cs b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationOptions.cs
index 12ff4f9956..02e9743b4a 100644
--- a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationOptions.cs
+++ b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationOptions.cs
@@ -105,6 +105,15 @@ namespace Microsoft.AspNet.Authentication.Cookies
[SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Logout", Justification = "By design")]
public PathString LogoutPath { get; set; }
+ ///
+ /// The AccessDeniedPath property informs the middleware that it should change an outgoing 403 Forbidden status
+ /// code into a 302 redirection onto the given path.
+ ///
+ /// If the AccessDeniedPath is null or empty, the middleware will not look for 403 Forbidden status codes, and it will
+ /// not redirect
+ ///
+ public PathString AccessDeniedPath { get; set; }
+
///
/// The ReturnUrlParameter determines the name of the query string parameter which is appended by the middleware
/// when a 401 Unauthorized status code is changed to a 302 redirect onto the login path. This is also the query
diff --git a/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationHandler.cs
index 42e6f838fb..7f41be810c 100644
--- a/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationHandler.cs
+++ b/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationHandler.cs
@@ -7,10 +7,9 @@ using System.IdentityModel.Tokens;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
+using Microsoft.AspNet.Authentication.Notifications;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication;
-using Microsoft.AspNet.Authentication;
-using Microsoft.AspNet.Authentication.Notifications;
using Microsoft.Framework.Logging;
using Microsoft.IdentityModel.Protocols;
@@ -195,6 +194,7 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
if (ShouldConvertChallengeToForbidden())
{
Response.StatusCode = 403;
+ return;
}
if ((Response.StatusCode != 401) || (ChallengeContext == null))
diff --git a/src/Microsoft.AspNet.Authentication/AutomaticAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication/AutomaticAuthenticationHandler.cs
index 5032e47fae..d91fadfcad 100644
--- a/src/Microsoft.AspNet.Authentication/AutomaticAuthenticationHandler.cs
+++ b/src/Microsoft.AspNet.Authentication/AutomaticAuthenticationHandler.cs
@@ -90,23 +90,5 @@ namespace Microsoft.AspNet.Authentication
return Options.AutomaticAuthentication && string.IsNullOrWhiteSpace(authenticationScheme);
}
-
- ///
- /// Override this method to deal with 401 challenge concerns, if an authentication scheme in question
- /// deals an authentication interaction as part of it's request flow. (like adding a response header, or
- /// changing the 401 result to 302 of a login page or external sign-in location.)
- ///
- ///
- protected override Task ApplyResponseChallengeAsync()
- {
- // If authenticate was called and the the status is still 401, authZ failed so set 403 and stop
- if (ShouldConvertChallengeToForbidden())
- {
- Response.StatusCode = 403;
- return Task.FromResult(0);
- }
- return base.ApplyResponseChallengeAsync();
- }
-
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs b/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs
index c1d5a0c329..51d9a9452c 100644
--- a/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs
+++ b/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs
@@ -420,6 +420,27 @@ namespace Microsoft.AspNet.Authentication.Cookies
transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Forbidden);
}
+ [Fact]
+ public async Task CookieTurns401ToAccessDeniedWhenSetAndIfAuthenticated()
+ {
+ var clock = new TestClock();
+ TestServer server = CreateServer(options =>
+ {
+ options.SystemClock = clock;
+ options.AccessDeniedPath = new PathString("/accessdenied");
+ },
+ SignInAsAlice);
+
+ Transaction transaction1 = await SendAsync(server, "http://example.com/testpath");
+
+ Transaction transaction2 = await SendAsync(server, "http://example.com/unauthorized", transaction1.CookieNameValue);
+
+ transaction2.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
+
+ Uri location = transaction2.Response.Headers.Location;
+ location.LocalPath.ShouldBe("/accessdenied");
+ }
+
[Fact]
public async Task CookieDoesNothingTo401IfNotAuthenticated()
{