[Fixes #1133] Limit the path on the nonce and correlation id cookies

This commit is contained in:
Javier Calvarro Nelson 2017-06-13 09:10:14 -07:00
parent 200ce72312
commit 879f0b7f40
8 changed files with 213 additions and 8 deletions

View File

@ -886,16 +886,21 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
throw new ArgumentNullException(nameof(nonce));
}
var options = new CookieOptions
{
HttpOnly = true,
SameSite = Http.SameSiteMode.None,
Path = OriginalPathBase + Options.CallbackPath,
Secure = Request.IsHttps,
Expires = Clock.UtcNow.Add(Options.ProtocolValidator.NonceLifetime)
};
Options.ConfigureNonceCookie?.Invoke(Context, options);
Response.Cookies.Append(
OpenIdConnectDefaults.CookieNoncePrefix + Options.StringDataFormat.Protect(nonce),
NonceProperty,
new CookieOptions
{
HttpOnly = true,
SameSite = Http.SameSiteMode.None,
Secure = Request.IsHttps,
Expires = Clock.UtcNow.Add(Options.ProtocolValidator.NonceLifetime)
});
options);
}
/// <summary>
@ -924,10 +929,13 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Path = OriginalPathBase + Options.CallbackPath,
SameSite = Http.SameSiteMode.None,
Secure = Request.IsHttps
};
Options.ConfigureNonceCookie?.Invoke(Context, cookieOptions);
Response.Cookies.Delete(nonceKey, cookieOptions);
return nonce;
}

View File

@ -262,5 +262,11 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect
/// remote OpenID Connect provider as an authorization/logout request parameter.
/// </summary>
public bool DisableTelemetry { get; set; }
/// <summary>
/// Gets or sets an action that can override the nonce cookie options before the
/// cookie gets added to the response.
/// </summary>
public Action<HttpContext, CookieOptions> ConfigureNonceCookie { get; set; }
}
}

View File

@ -87,6 +87,8 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
Secure = Request.IsHttps
};
Options.ConfigureStateCookie?.Invoke(Context, cookieOptions);
Response.Cookies.Delete(StateCookie, cookieOptions);
var accessToken = await ObtainAccessTokenAsync(requestToken, oauthVerifier);
@ -159,6 +161,8 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
Expires = Clock.UtcNow.Add(Options.RemoteAuthenticationTimeout),
};
Options.ConfigureStateCookie?.Invoke(Context, cookieOptions);
Response.Cookies.Append(StateCookie, Options.StateDataFormat.Protect(requestToken), cookieOptions);
var redirectContext = new TwitterRedirectToAuthorizationEndpointContext(Context, Scheme, Options, properties, twitterAuthenticationEndpoint);

View File

@ -58,6 +58,12 @@ namespace Microsoft.AspNetCore.Authentication.Twitter
/// </summary>
public ISecureDataFormat<RequestToken> StateDataFormat { get; set; }
/// <summary>
/// Gets or sets an action that can override the state cookie options before the
/// cookie gets added to the response.
/// </summary>
public Action<HttpContext, CookieOptions> ConfigureStateCookie { get; set; }
/// <summary>
/// Gets or sets the <see cref="TwitterEvents"/> used to handle authentication events.
/// </summary>

View File

@ -205,9 +205,12 @@ namespace Microsoft.AspNetCore.Authentication
HttpOnly = true,
SameSite = SameSiteMode.None,
Secure = Request.IsHttps,
Path = OriginalPathBase + Options.CallbackPath,
Expires = Clock.UtcNow.Add(Options.RemoteAuthenticationTimeout),
};
Options.ConfigureCorrelationIdCookie?.Invoke(Context, cookieOptions);
properties.Items[CorrelationProperty] = correlationId;
var cookieName = CorrelationPrefix + Scheme.Name + "." + correlationId;
@ -243,9 +246,13 @@ namespace Microsoft.AspNetCore.Authentication
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Path = OriginalPathBase + Options.CallbackPath,
SameSite = SameSiteMode.None,
Secure = Request.IsHttps
};
Options.ConfigureCorrelationIdCookie?.Invoke(Context, cookieOptions);
Response.Cookies.Delete(cookieName, cookieOptions);
if (!string.Equals(correlationCookie, CorrelationMarker, StringComparison.Ordinal))

View File

@ -87,5 +87,11 @@ namespace Microsoft.AspNetCore.Authentication
/// the size of the final authentication cookie.
/// </summary>
public bool SaveTokens { get; set; }
/// <summary>
/// Gets or sets an action that can override the correlation id cookie options before the
/// cookie gets added to the response.
/// </summary>
public Action<HttpContext, CookieOptions> ConfigureCorrelationIdCookie { get; set; }
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
@ -165,6 +166,70 @@ namespace Microsoft.AspNetCore.Authentication.OAuth
Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
}
[Fact]
public async Task RedirectToIdentityProvider_SetsCorrelationIdCookiePath_ToCallBackPath()
{
var server = CreateServer(
app => { },
s => s.AddOAuthAuthentication(
"Weblie",
opt =>
{
opt.ClientId = "Test Id";
opt.ClientSecret = "secret";
opt.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opt.AuthorizationEndpoint = "https://example.com/provider/login";
opt.TokenEndpoint = "https://example.com/provider/token";
opt.CallbackPath = "/oauth-callback";
}),
ctx =>
{
ctx.ChallengeAsync("Weblie").ConfigureAwait(false).GetAwaiter().GetResult();
return true;
});
var transaction = await server.SendAsync("https://www.example.com/challenge");
var res = transaction.Response;
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
Assert.NotNull(res.Headers.Location);
var setCookie = Assert.Single(res.Headers, h => h.Key == "Set-Cookie");
var correlation = Assert.Single(setCookie.Value, v => v.StartsWith(".AspNetCore.Correlation."));
Assert.Contains("path=/oauth-callback", correlation);
}
[Fact]
public async Task RedirectToAuthorizeEndpoint_CorrelationIdCookieOptions_CanBeOverriden()
{
var server = CreateServer(
app => { },
s => s.AddOAuthAuthentication(
"Weblie",
opt =>
{
opt.ClientId = "Test Id";
opt.ClientSecret = "secret";
opt.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opt.AuthorizationEndpoint = "https://example.com/provider/login";
opt.TokenEndpoint = "https://example.com/provider/token";
opt.CallbackPath = "/oauth-callback";
opt.ConfigureCorrelationIdCookie = (ctx, options) => options.Path = "/";
}),
ctx =>
{
ctx.ChallengeAsync("Weblie").ConfigureAwait(false).GetAwaiter().GetResult();
return true;
});
var transaction = await server.SendAsync("https://www.example.com/challenge");
var res = transaction.Response;
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
Assert.NotNull(res.Headers.Location);
var setCookie = Assert.Single(res.Headers, h => h.Key == "Set-Cookie");
var correlation = Assert.Single(setCookie.Value, v => v.StartsWith(".AspNetCore.Correlation."));
Assert.Contains("path=/", correlation);
}
private static TestServer CreateServer(Action<IApplicationBuilder> configure, Action<IServiceCollection> configureServices, Func<HttpContext, bool> handler)
{

View File

@ -60,6 +60,108 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
OpenIdConnectParameterNames.VersionTelemetry);
}
[Fact]
public async Task RedirectToIdentityProvider_SetsNonceCookiePath_ToCallBackPath()
{
var setting = new TestSettings(opt =>
{
opt.ClientId = "Test Id";
opt.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opt.Configuration = new OpenIdConnectConfiguration
{
AuthorizationEndpoint = "https://example.com/provider/login"
};
});
var server = setting.CreateTestServer();
var transaction = await server.SendAsync(DefaultHost + TestServerBuilder.Challenge);
var res = transaction.Response;
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
Assert.NotNull(res.Headers.Location);
var setCookie = Assert.Single(res.Headers, h => h.Key == "Set-Cookie");
var nonce = Assert.Single(setCookie.Value, v => v.StartsWith(OpenIdConnectDefaults.CookieNoncePrefix));
Assert.Contains("path=/signin-oidc", nonce);
}
[Fact]
public async Task RedirectToIdentityProvider_NonceCookieOptions_CanBeOverriden()
{
var setting = new TestSettings(opt =>
{
opt.ClientId = "Test Id";
opt.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opt.Configuration = new OpenIdConnectConfiguration
{
AuthorizationEndpoint = "https://example.com/provider/login"
};
opt.ConfigureNonceCookie = (ctx, options) => options.Path = "/";
});
var server = setting.CreateTestServer();
var transaction = await server.SendAsync(DefaultHost + TestServerBuilder.Challenge);
var res = transaction.Response;
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
Assert.NotNull(res.Headers.Location);
var setCookie = Assert.Single(res.Headers, h => h.Key == "Set-Cookie");
var nonce = Assert.Single(setCookie.Value, v => v.StartsWith(OpenIdConnectDefaults.CookieNoncePrefix));
Assert.Contains("path=/", nonce);
}
[Fact]
public async Task RedirectToIdentityProvider_SetsCorrelationIdCookiePath_ToCallBackPath()
{
var setting = new TestSettings(opt =>
{
opt.ClientId = "Test Id";
opt.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opt.Configuration = new OpenIdConnectConfiguration
{
AuthorizationEndpoint = "https://example.com/provider/login"
};
});
var server = setting.CreateTestServer();
var transaction = await server.SendAsync(DefaultHost + TestServerBuilder.Challenge);
var res = transaction.Response;
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
Assert.NotNull(res.Headers.Location);
var setCookie = Assert.Single(res.Headers, h => h.Key == "Set-Cookie");
var correlation = Assert.Single(setCookie.Value, v => v.StartsWith(".AspNetCore.Correlation."));
Assert.Contains("path=/signin-oidc", correlation);
}
[Fact]
public async Task RedirectToIdentityProvider_CorrelationIdCookieOptions_CanBeOverriden()
{
var setting = new TestSettings(opt =>
{
opt.ClientId = "Test Id";
opt.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opt.Configuration = new OpenIdConnectConfiguration
{
AuthorizationEndpoint = "https://example.com/provider/login"
};
opt.ConfigureCorrelationIdCookie = (ctx, options) => options.Path = "/";
});
var server = setting.CreateTestServer();
var transaction = await server.SendAsync(DefaultHost + TestServerBuilder.Challenge);
var res = transaction.Response;
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
Assert.NotNull(res.Headers.Location);
var setCookie = Assert.Single(res.Headers, h => h.Key == "Set-Cookie");
var correlation = Assert.Single(setCookie.Value, v => v.StartsWith(".AspNetCore.Correlation."));
Assert.Contains("path=/", correlation);
}
[Fact]
public async Task EndSessionRequestDoesNotIncludeTelemetryParametersWhenDisabled()
{
@ -173,7 +275,8 @@ namespace Microsoft.AspNetCore.Authentication.Test.OpenIdConnect
[Fact]
public async Task SignOut_WithMissingConfig_Throws()
{
var setting = new TestSettings(opt => {
var setting = new TestSettings(opt =>
{
opt.ClientId = "Test Id";
opt.Configuration = new OpenIdConnectConfiguration();
});