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.Threading.Tasks;
using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Authentication.Cookies
{
@ -36,12 +37,75 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// <summary>
/// A delegate assigned to this property will be invoked when the related method is called.
/// </summary>
public Func<CookieRedirectContext, Task> OnRedirect { get; set; } = context =>
public Func<CookieRedirectContext, Task> OnRedirectToLogin { get; set; } = context =>
{
context.Response.Redirect(context.RedirectUri);
if (IsAjaxRequest(context.Request))
{
context.Response.Headers["Location"] = context.RedirectUri;
context.Response.StatusCode = 401;
}
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> 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>
/// Implements the interface method by invoking the related delegate method.
/// </summary>
@ -71,24 +135,24 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// Implements the interface method by invoking the related delegate method.
/// </summary>
/// <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>
/// Implements the interface method by invoking the related delegate method.
/// </summary>
/// <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>
/// Implements the interface method by invoking the related delegate method.
/// </summary>
/// <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>
/// Implements the interface method by invoking the related delegate method.
/// </summary>
/// <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);
}
[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]
[InlineData(true)]
[InlineData(false)]
@ -225,9 +286,9 @@ namespace Microsoft.AspNet.Authentication.Cookies
var server = CreateServer(options =>
{
options.SystemClock = clock;
},
SignInAsAlice,
baseAddress: null,
},
SignInAsAlice,
baseAddress: null,
claimsTransform: o => o.Transformer = new ClaimsTransformer
{
OnTransform = p =>
@ -401,7 +462,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies")))));
var transaction1 = await SendAsync(server, "http://example.com/testpath");
var transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
Assert.NotNull(transaction2.SetCookie);
Assert.Equal("Alice", FindClaimValue(transaction2, ClaimTypes.Name));
@ -554,7 +615,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
context =>
{
Assert.Equal(new PathString("/base"), context.Request.PathBase);
return context.Authentication.SignInAsync("Cookies",
return context.Authentication.SignInAsync("Cookies",
new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies"))));
},
new Uri("http://example.com/base"));
@ -573,7 +634,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
{
options.AutomaticAuthenticate = automatic;
options.SystemClock = clock;
},
},
SignInAsAlice);
var transaction1 = await SendAsync(server, "http://example.com/testpath");
@ -708,7 +769,8 @@ namespace Microsoft.AspNet.Authentication.Cookies
var server = TestServer.Create(app =>
{
app.UseCookieAuthentication();
app.Run(async context => {
app.Run(async context =>
{
await Assert.ThrowsAsync<InvalidOperationException>(() => context.Authentication.ChallengeAsync());
});
}, services => services.AddAuthentication());
@ -739,7 +801,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
var server = TestServer.Create(app =>
{
app.UseCookieAuthentication(options => options.LoginPath = new PathString("/login"));
app.Map("/notlogin", signoutApp => signoutApp.Run(context => context.Authentication.SignInAsync("Cookies",
app.Map("/notlogin", signoutApp => signoutApp.Run(context => context.Authentication.SignInAsync("Cookies",
new ClaimsPrincipal())));
},
services => services.AddAuthentication());
@ -970,6 +1032,10 @@ namespace Microsoft.AspNet.Authentication.Cookies
{
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"))
{
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");
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 =>
{
app.Properties["host.AppName"] = "Microsoft.Owin.Security.Tests";
@ -146,8 +145,7 @@ namespace Microsoft.AspNet.CookiePolicy.Test
identity.AddClaim(new Claim(ClaimTypes.Name, "Alice"));
user.AddIdentity(identity);
var dataProtection = new DataProtection.DataProtectionProvider(new DirectoryInfo("."));
var dataProtection = new DataProtection.DataProtectionProvider(new DirectoryInfo("..\\..\\artifacts"));
var newServer = TestHost.TestServer.Create(app =>
{
app.UseCookieAuthentication(options => options.DataProtectionProvider = dataProtection);