Cookies no longer redirects for AJAX requests

This commit is contained in:
Hao Kung 2015-10-23 14:39:42 -07:00
parent 35b7248734
commit 1d2c6ba122
3 changed files with 146 additions and 18 deletions

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Authentication.Cookies namespace Microsoft.AspNet.Authentication.Cookies
{ {
@ -36,12 +37,75 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// <summary> /// <summary>
/// A delegate assigned to this property will be invoked when the related method is called. /// A delegate assigned to this property will be invoked when the related method is called.
/// </summary> /// </summary>
public Func<CookieRedirectContext, Task> OnRedirect { get; set; } = context => public Func<CookieRedirectContext, Task> OnRedirectToLogin { get; set; } = context =>
{
if (IsAjaxRequest(context.Request))
{
context.Response.Headers["Location"] = context.RedirectUri;
context.Response.StatusCode = 401;
}
else
{ {
context.Response.Redirect(context.RedirectUri); context.Response.Redirect(context.RedirectUri);
}
return Task.FromResult(0); return Task.FromResult(0);
}; };
/// <summary>
/// A delegate assigned to this property will be invoked when the related method is called.
/// </summary>
public Func<CookieRedirectContext, Task> OnRedirectToAccessDenied { get; set; } = context =>
{
if (IsAjaxRequest(context.Request))
{
context.Response.Headers["Location"] = context.RedirectUri;
context.Response.StatusCode = 403;
}
else
{
context.Response.Redirect(context.RedirectUri);
}
return Task.FromResult(0);
};
/// <summary>
/// A delegate assigned to this property will be invoked when the related method is called.
/// </summary>
public Func<CookieRedirectContext, Task> OnRedirectToLogout { get; set; } = context =>
{
if (IsAjaxRequest(context.Request))
{
context.Response.Headers["Location"] = context.RedirectUri;
}
else
{
context.Response.Redirect(context.RedirectUri);
}
return Task.FromResult(0);
};
/// <summary>
/// A delegate assigned to this property will be invoked when the related method is called.
/// </summary>
public Func<CookieRedirectContext, Task> OnRedirectToReturnUrl { get; set; } = context =>
{
if (IsAjaxRequest(context.Request))
{
context.Response.Headers["Location"] = context.RedirectUri;
}
else
{
context.Response.Redirect(context.RedirectUri);
}
return Task.FromResult(0);
};
private static bool IsAjaxRequest(HttpRequest request)
{
return request.Query["X-Requested-With"] == "XMLHttpRequest" ||
request.Headers["X-Requested-With"] == "XMLHttpRequest";
}
/// <summary> /// <summary>
/// Implements the interface method by invoking the related delegate method. /// Implements the interface method by invoking the related delegate method.
/// </summary> /// </summary>
@ -71,24 +135,24 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// Implements the interface method by invoking the related delegate method. /// Implements the interface method by invoking the related delegate method.
/// </summary> /// </summary>
/// <param name="context">Contains information about the event</param> /// <param name="context">Contains information about the event</param>
public virtual Task RedirectToLogout(CookieRedirectContext context) => OnRedirect(context); public virtual Task RedirectToLogout(CookieRedirectContext context) => OnRedirectToLogout(context);
/// <summary> /// <summary>
/// Implements the interface method by invoking the related delegate method. /// Implements the interface method by invoking the related delegate method.
/// </summary> /// </summary>
/// <param name="context">Contains information about the event</param> /// <param name="context">Contains information about the event</param>
public virtual Task RedirectToLogin(CookieRedirectContext context) => OnRedirect(context); public virtual Task RedirectToLogin(CookieRedirectContext context) => OnRedirectToLogin(context);
/// <summary> /// <summary>
/// Implements the interface method by invoking the related delegate method. /// Implements the interface method by invoking the related delegate method.
/// </summary> /// </summary>
/// <param name="context">Contains information about the event</param> /// <param name="context">Contains information about the event</param>
public virtual Task RedirectToReturnUrl(CookieRedirectContext context) => OnRedirect(context); public virtual Task RedirectToReturnUrl(CookieRedirectContext context) => OnRedirectToReturnUrl(context);
/// <summary> /// <summary>
/// Implements the interface method by invoking the related delegate method. /// Implements the interface method by invoking the related delegate method.
/// </summary> /// </summary>
/// <param name="context">Contains information about the event</param> /// <param name="context">Contains information about the event</param>
public virtual Task RedirectToAccessDenied(CookieRedirectContext context) => OnRedirect(context); public virtual Task RedirectToAccessDenied(CookieRedirectContext context) => OnRedirectToAccessDenied(context);
} }
} }

View File

@ -33,6 +33,67 @@ namespace Microsoft.AspNet.Authentication.Cookies
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
} }
[Fact]
public async Task AjaxLoginRedirectToReturnUrlTurnsInto200WithLocationHeader()
{
var server = CreateServer(options =>
{
options.AutomaticChallenge = true;
options.LoginPath = "/login";
});
var transaction = await SendAsync(server, "http://example.com/protected?X-Requested-With=XMLHttpRequest");
Assert.Equal(HttpStatusCode.Unauthorized, transaction.Response.StatusCode);
var responded = transaction.Response.Headers.GetValues("Location");
Assert.Equal(1, responded.Count());
Assert.True(responded.Single().StartsWith("http://example.com/login"));
}
[Fact]
public async Task AjaxForbidTurnsInto403WithLocationHeader()
{
var server = CreateServer(options =>
{
options.AccessDeniedPath = "/denied";
});
var transaction = await SendAsync(server, "http://example.com/forbid?X-Requested-With=XMLHttpRequest");
Assert.Equal(HttpStatusCode.Forbidden, transaction.Response.StatusCode);
var responded = transaction.Response.Headers.GetValues("Location");
Assert.Equal(1, responded.Count());
Assert.True(responded.Single().StartsWith("http://example.com/denied"));
}
[Fact]
public async Task AjaxLogoutRedirectToReturnUrlTurnsInto200WithLocationHeader()
{
var server = CreateServer(options =>
{
options.LogoutPath = "/signout";
});
var transaction = await SendAsync(server, "http://example.com/signout?X-Requested-With=XMLHttpRequest&ReturnUrl=/");
Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
var responded = transaction.Response.Headers.GetValues("Location");
Assert.Equal(1, responded.Count());
Assert.True(responded.Single().StartsWith("/"));
}
[Fact]
public async Task AjaxChallengeRedirectTurnsInto200WithLocationHeader()
{
var server = CreateServer(options =>
{
});
var transaction = await SendAsync(server, "http://example.com/challenge?X-Requested-With=XMLHttpRequest&ReturnUrl=/");
Assert.Equal(HttpStatusCode.Unauthorized, transaction.Response.StatusCode);
var responded = transaction.Response.Headers.GetValues("Location");
Assert.Equal(1, responded.Count());
Assert.True(responded.Single().StartsWith("http://example.com/Account/Login"));
}
[Theory] [Theory]
[InlineData(true)] [InlineData(true)]
[InlineData(false)] [InlineData(false)]
@ -708,7 +769,8 @@ namespace Microsoft.AspNet.Authentication.Cookies
var server = TestServer.Create(app => var server = TestServer.Create(app =>
{ {
app.UseCookieAuthentication(); app.UseCookieAuthentication();
app.Run(async context => { app.Run(async context =>
{
await Assert.ThrowsAsync<InvalidOperationException>(() => context.Authentication.ChallengeAsync()); await Assert.ThrowsAsync<InvalidOperationException>(() => context.Authentication.ChallengeAsync());
}); });
}, services => services.AddAuthentication()); }, services => services.AddAuthentication());
@ -970,6 +1032,10 @@ namespace Microsoft.AspNet.Authentication.Cookies
{ {
await context.Authentication.ChallengeAsync(CookieAuthenticationDefaults.AuthenticationScheme); await context.Authentication.ChallengeAsync(CookieAuthenticationDefaults.AuthenticationScheme);
} }
else if (req.Path == new PathString("/signout"))
{
await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
else if (req.Path == new PathString("/unauthorized")) else if (req.Path == new PathString("/unauthorized"))
{ {
await context.Authentication.ChallengeAsync(CookieAuthenticationDefaults.AuthenticationScheme, new AuthenticationProperties(), ChallengeBehavior.Unauthorized); await context.Authentication.ChallengeAsync(CookieAuthenticationDefaults.AuthenticationScheme, new AuthenticationProperties(), ChallengeBehavior.Unauthorized);

View File

@ -106,8 +106,7 @@ namespace Microsoft.AspNet.CookiePolicy.Test
var identity = new ClaimsIdentity("Cookies"); var identity = new ClaimsIdentity("Cookies");
identity.AddClaim(new Claim(ClaimTypes.Name, "Alice")); identity.AddClaim(new Claim(ClaimTypes.Name, "Alice"));
var dataProtection = new DataProtection.DataProtectionProvider(new DirectoryInfo(".")); var dataProtection = new DataProtection.DataProtectionProvider(new DirectoryInfo("..\\..\\artifacts"));
var interopServer = TestServer.Create(app => var interopServer = TestServer.Create(app =>
{ {
app.Properties["host.AppName"] = "Microsoft.Owin.Security.Tests"; app.Properties["host.AppName"] = "Microsoft.Owin.Security.Tests";
@ -146,8 +145,7 @@ namespace Microsoft.AspNet.CookiePolicy.Test
identity.AddClaim(new Claim(ClaimTypes.Name, "Alice")); identity.AddClaim(new Claim(ClaimTypes.Name, "Alice"));
user.AddIdentity(identity); user.AddIdentity(identity);
var dataProtection = new DataProtection.DataProtectionProvider(new DirectoryInfo(".")); var dataProtection = new DataProtection.DataProtectionProvider(new DirectoryInfo("..\\..\\artifacts"));
var newServer = TestHost.TestServer.Create(app => var newServer = TestHost.TestServer.Create(app =>
{ {
app.UseCookieAuthentication(options => options.DataProtectionProvider = dataProtection); app.UseCookieAuthentication(options => options.DataProtectionProvider = dataProtection);