#404 Verify state via independent cookie.

This commit is contained in:
Chris R 2015-09-09 12:16:22 -07:00
parent 9f7a723843
commit ebcad24307
6 changed files with 88 additions and 151 deletions

View File

@ -32,8 +32,8 @@ namespace OpenIdConnectSample
app.UseOpenIdConnectAuthentication(options =>
{
options.ClientId = "fe78e0b4-6fe7-47e6-812c-fb75cee266a4";
options.Authority = "https://login.windows.net/cyrano.onmicrosoft.com";
options.ClientId = "63a87a83-64b9-4ac1-b2c5-092126f8474f";
options.Authority = "https://login.windows.net/tratcheroutlook.onmicrosoft.com";
options.RedirectUri = "http://localhost:42023";
});

View File

@ -24,15 +24,15 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
public const string Caption = "OpenIdConnect";
/// <summary>
/// The prefix used to provide a default OpenIdConnectAuthenticationOptions.CookieName.
/// </summary>
public const string CookiePrefix = ".AspNet.OpenIdConnect.";
/// <summary>
/// The prefix used to for the a nonce in the cookie.
/// The prefix used to for the nonce in the cookie.
/// </summary>
public const string CookieNoncePrefix = ".AspNet.OpenIdConnect.Nonce.";
/// <summary>
/// The prefix used for the state in the cookie.
/// </summary>
public const string CookieStatePrefix = ".AspNet.OpenIdConnect.State.";
/// <summary>
/// The property for the RedirectUri that was used when asking for a 'authorizationCode'.
/// </summary>

View File

@ -11,6 +11,7 @@ using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
@ -48,6 +49,8 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
</body>
</html>";
private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create();
private OpenIdConnectConfiguration _configuration;
protected HttpClient Backchannel { get; private set; }
@ -220,6 +223,8 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
}
GenerateCorrelationId(properties);
var redirectToIdentityProviderContext = new RedirectToIdentityProviderContext(Context, Options)
{
ProtocolMessage = message
@ -393,6 +398,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
throw new SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0006_MessageContainsError, message.Error, message.ErrorDescription ?? "ErrorDecription null", message.ErrorUri ?? "ErrorUri null"));
}
if (!ValidateCorrelationId(properties))
{
return null;
}
if (_configuration == null && Options.ConfigurationManager != null)
{
Logger.LogVerbose(Resources.OIDCH_0007_UpdatingConfiguration);
@ -714,6 +724,66 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
return null;
}
private void GenerateCorrelationId([NotNull] AuthenticationProperties properties)
{
var correlationKey = OpenIdConnectAuthenticationDefaults.CookieStatePrefix;
var nonceBytes = new byte[32];
CryptoRandom.GetBytes(nonceBytes);
var correlationId = TextEncodings.Base64Url.Encode(nonceBytes);
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Secure = Request.IsHttps,
Expires = DateTime.UtcNow + Options.ProtocolValidator.NonceLifetime
};
properties.Items[correlationKey] = correlationId;
Response.Cookies.Append(correlationKey + correlationId, NonceProperty, cookieOptions);
}
private bool ValidateCorrelationId([NotNull] AuthenticationProperties properties)
{
var correlationKey = OpenIdConnectAuthenticationDefaults.CookieStatePrefix;
string correlationId;
if (!properties.Items.TryGetValue(
correlationKey,
out correlationId))
{
Logger.LogWarning("{0} state property not found.", correlationKey);
return false;
}
properties.Items.Remove(correlationKey);
var cookieName = correlationKey + correlationId;
var correlationCookie = Request.Cookies[cookieName];
if (string.IsNullOrEmpty(correlationCookie))
{
Logger.LogWarning("{0} cookie not found.", cookieName);
return false;
}
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Secure = Request.IsHttps
};
Response.Cookies.Delete(cookieName, cookieOptions);
if (!string.Equals(correlationCookie, NonceProperty, StringComparison.Ordinal))
{
Logger.LogWarning("{0} correlation cookie and state property mismatch.", correlationKey);
return false;
}
return true;
}
private AuthenticationProperties GetPropertiesFromState(string state)
{
// assume a well formed query string: <a=b&>OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey=kasjd;fljasldkjflksdj<&c=d>

View File

@ -169,7 +169,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
public string ExpectedState
{
get { return OpenIdConnectParameterNames.State + "=" + Encoder.UrlEncode(State); }
get { return Encoder.UrlEncode(State); }
}
}
}

View File

@ -119,145 +119,6 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
};
}
[Theory, MemberData("AuthenticateCoreDataSet")]
public async Task AuthenticateCore(LogLevel logLevel, int[] expectedLogIndexes, Action<OpenIdConnectAuthenticationOptions> action, OpenIdConnectMessage message)
{
var errors = new List<Tuple<LogEntry, LogEntry>>();
var expectedLogs = LoggingUtilities.PopulateLogEntries(expectedLogIndexes);
var handler = new OpenIdConnectAuthenticationHandlerForTestingAuthenticate();
var loggerFactory = new InMemoryLoggerFactory(logLevel);
var server = CreateServer(new ConfigureOptions<OpenIdConnectAuthenticationOptions>(action), UrlEncoder.Default, loggerFactory, handler);
await server.CreateClient().PostAsync("http://localhost", new FormUrlEncodedContent(message.Parameters));
LoggingUtilities.CheckLogs(loggerFactory.Logger.Logs, expectedLogs, errors);
Debug.WriteLine(LoggingUtilities.LoggingErrors(errors));
Assert.True(errors.Count == 0, LoggingUtilities.LoggingErrors(errors));
}
public static TheoryData<LogLevel, int[], Action<OpenIdConnectAuthenticationOptions>, OpenIdConnectMessage> AuthenticateCoreDataSet
{
get
{
var formater = new AuthenticationPropertiesFormaterKeyValue();
var dataset = new TheoryData<LogLevel, int[], Action<OpenIdConnectAuthenticationOptions>, OpenIdConnectMessage>();
var properties = new AuthenticationProperties();
var message = new OpenIdConnectMessage();
var validState = UrlEncoder.Default.UrlEncode(formater.Protect(properties));
message.State = validState;
// MessageReceived - Handled / Skipped
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 2 }, MessageReceivedHandledOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 2 }, MessageReceivedHandledOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, MessageReceivedHandledOptions, message);
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 3 }, MessageReceivedSkippedOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 3 }, MessageReceivedSkippedOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, MessageReceivedSkippedOptions, message);
// State - null, empty string, invalid
message = new OpenIdConnectMessage();
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 4, 7, 28 }, StateNullOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 4, 7 }, StateNullOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, StateNullOptions, message);
message = new OpenIdConnectMessage();
message.State = string.Empty;
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 4, 7, 28 }, StateEmptyOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 4, 7 }, StateEmptyOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, StateEmptyOptions, message);
message = new OpenIdConnectMessage();
message.State = Guid.NewGuid().ToString();
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 5 }, StateInvalidOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 5 }, StateInvalidOptions, message);
dataset.Add(LogLevel.Error, new int[] { 5 }, StateInvalidOptions, message);
// OpenIdConnectMessage.Error != null
message = new OpenIdConnectMessage();
message.Error = "Error";
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 4, 6, 17, 18 }, MessageWithErrorOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 4, 6, 17, 18 }, MessageWithErrorOptions, message);
dataset.Add(LogLevel.Error, new int[] { 6, 17 }, MessageWithErrorOptions, message);
// SecurityTokenReceived - Handled / Skipped
message = new OpenIdConnectMessage();
message.IdToken = "invalid";
message.State = validState;
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20, 8 }, SecurityTokenReceivedHandledOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 7, 8 }, SecurityTokenReceivedHandledOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, SecurityTokenReceivedHandledOptions, message);
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20, 9 }, SecurityTokenReceivedSkippedOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 7, 9 }, SecurityTokenReceivedSkippedOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, SecurityTokenReceivedSkippedOptions, message);
// SecurityTokenValidation - ReturnsNull, Throws, Validates
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20, 11, 17, 18 }, SecurityTokenValidatorCannotReadToken, message);
dataset.Add(LogLevel.Verbose, new int[] { 7, 11, 17, 18 }, SecurityTokenValidatorCannotReadToken, message);
dataset.Add(LogLevel.Error, new int[] { 11, 17 }, SecurityTokenValidatorCannotReadToken, message);
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20, 17, 21, 18 }, SecurityTokenValidatorThrows, message);
dataset.Add(LogLevel.Verbose, new int[] { 7, 17, 21, 18 }, SecurityTokenValidatorThrows, message);
dataset.Add(LogLevel.Error, new int[] { 17 }, SecurityTokenValidatorThrows, message);
message.Nonce = nonceForJwt;
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20 }, SecurityTokenValidatorValidatesAllTokens, message);
dataset.Add(LogLevel.Verbose, new int[] { 7 }, SecurityTokenValidatorValidatesAllTokens, message);
dataset.Add(LogLevel.Error, new int[] { }, SecurityTokenValidatorValidatesAllTokens, message);
// SecurityTokenValidation - Handled / Skipped
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20, 12 }, SecurityTokenValidatedHandledOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 7, 12 }, SecurityTokenValidatedHandledOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, SecurityTokenValidatedHandledOptions, message);
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20, 13 }, SecurityTokenValidatedSkippedOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 7, 13 }, SecurityTokenValidatedSkippedOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, SecurityTokenValidatedSkippedOptions, message);
// AuthenticationCodeReceived - Handled / Skipped
message = new OpenIdConnectMessage();
message.Code = Guid.NewGuid().ToString();
message.State = validState;
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 14, 15 }, AuthorizationCodeReceivedHandledOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 7, 15 }, AuthorizationCodeReceivedHandledOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, AuthorizationCodeReceivedHandledOptions, message);
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 14, 16 }, AuthorizationCodeReceivedSkippedOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 7, 16 }, AuthorizationCodeReceivedSkippedOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, AuthorizationCodeReceivedSkippedOptions, message);
message = new OpenIdConnectMessage();
message.Code = Guid.NewGuid().ToString();
message.State = validState;
message.IdToken = "test token";
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20, 14, 15 }, AuthorizationCodeReceivedHandledOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 7, 15 }, AuthorizationCodeReceivedHandledOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, AuthorizationCodeReceivedHandledOptions, message);
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20, 14, 16 }, AuthorizationCodeReceivedSkippedOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 7, 16 }, AuthorizationCodeReceivedSkippedOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, AuthorizationCodeReceivedSkippedOptions, message);
// CodeReceivedAndRedeemed and GetUserInformationFromUIEndpoint
message = new OpenIdConnectMessage();
message.IdToken = null;
message.Code = Guid.NewGuid().ToString();
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 4, 7, 14, 23, 25, 26 }, CodeReceivedAndRedeemedHandledOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 4, 7, 26 }, CodeReceivedAndRedeemedHandledOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, CodeReceivedAndRedeemedHandledOptions, message);
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 4, 7, 14, 23, 25, 27 }, CodeReceivedAndRedeemedSkippedOptions, message);
dataset.Add(LogLevel.Verbose, new int[] { 4, 7, 27 }, CodeReceivedAndRedeemedSkippedOptions, message);
dataset.Add(LogLevel.Error, new int[] { }, CodeReceivedAndRedeemedSkippedOptions, message);
dataset.Add(LogLevel.Debug, new int[] { 0, 1, 4, 7, 14, 23, 25, 24, 12 }, GetUserInfoFromUIEndpoint, message);
dataset.Add(LogLevel.Verbose, new int[] { 4, 7, 12 }, GetUserInfoFromUIEndpoint, message);
dataset.Add(LogLevel.Error, new int[] { }, GetUserInfoFromUIEndpoint, message);
return dataset;
}
}
#region Configure Options for AuthenticateCore variations
private static void DefaultOptions(OpenIdConnectAuthenticationOptions options)

View File

@ -73,7 +73,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
}
[Fact]
public async Task ChallengeWillSetNonceCookie()
public async Task ChallengeWillSetNonceAndStateCookies()
{
var server = CreateServer(options =>
{
@ -82,8 +82,14 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
options.Configuration = TestUtilities.DefaultOpenIdConnectConfiguration;
});
var transaction = await SendAsync(server, DefaultHost + Challenge);
transaction.SetCookie.Single().ShouldContain(OpenIdConnectAuthenticationDefaults.CookieNoncePrefix);
transaction.SetCookie.Single().ShouldContain("Expires");
var firstCookie = transaction.SetCookie.First();
firstCookie.ShouldContain(OpenIdConnectAuthenticationDefaults.CookieNoncePrefix);
firstCookie.ShouldContain("Expires");
var secondCookie = transaction.SetCookie.Skip(1).First();
secondCookie.ShouldContain(OpenIdConnectAuthenticationDefaults.CookieStatePrefix);
secondCookie.ShouldContain("Expires");
}
[Fact]