diff --git a/samples/OpenIdConnectSample/Properties/launchSettings.json b/samples/OpenIdConnectSample/Properties/launchSettings.json index d8622657cf..a744c05c54 100644 --- a/samples/OpenIdConnectSample/Properties/launchSettings.json +++ b/samples/OpenIdConnectSample/Properties/launchSettings.json @@ -10,12 +10,12 @@ "kestrel": { "commandName": "kestrel", "launchBrowser": true, - "launchUrl": "http://localhost:5004" + "launchUrl": "http://localhost:42023" }, "web": { "commandName": "web", "launchBrowser": true, - "launchUrl": "http://localhost:12345" + "launchUrl": "http://localhost:42023" } } } \ No newline at end of file diff --git a/samples/OpenIdConnectSample/Startup.cs b/samples/OpenIdConnectSample/Startup.cs index 3a0a289e72..177e866d7b 100644 --- a/samples/OpenIdConnectSample/Startup.cs +++ b/samples/OpenIdConnectSample/Startup.cs @@ -6,6 +6,7 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Authentication; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.Logging; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; namespace OpenIdConnectSample { @@ -28,8 +29,10 @@ namespace OpenIdConnectSample app.UseOpenIdConnectAuthentication(options => { options.ClientId = "63a87a83-64b9-4ac1-b2c5-092126f8474f"; + options.ClientSecret = "Yse2iP7tO1Azq0iDajNisMaTSnIDv+FXmAsFuXr+Cy8="; // for code flow options.Authority = "https://login.windows.net/tratcheroutlook.onmicrosoft.com"; options.RedirectUri = "http://localhost:42023"; + options.ResponseType = OpenIdConnectResponseTypes.Code; }); app.Run(async context => diff --git a/samples/OpenIdConnectSample/project.json b/samples/OpenIdConnectSample/project.json index ad9c36c6f7..2dbf30c6bd 100644 --- a/samples/OpenIdConnectSample/project.json +++ b/samples/OpenIdConnectSample/project.json @@ -13,8 +13,8 @@ "dnxcore50": { } }, "commands": { - "web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:12345", - "kestrel": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.Kestrel --server.urls http://localhost:5004" + "web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:42023", + "kestrel": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.Kestrel --server.urls http://localhost:42023" }, "webroot": "wwwroot" } diff --git a/src/Microsoft.AspNet.Authentication.JwtBearer/project.json b/src/Microsoft.AspNet.Authentication.JwtBearer/project.json index f05599e083..722ed90f88 100644 --- a/src/Microsoft.AspNet.Authentication.JwtBearer/project.json +++ b/src/Microsoft.AspNet.Authentication.JwtBearer/project.json @@ -8,7 +8,7 @@ "dependencies": { "Microsoft.AspNet.Authentication": "1.0.0-*", "Microsoft.Framework.NotNullAttribute.Sources": { "type": "build", "version": "1.0.0-*" }, - "Microsoft.IdentityModel.Protocols.OpenIdConnect": "2.0.0-beta7-*" + "Microsoft.IdentityModel.Protocols.OpenIdConnect": "2.0.0-beta8-*" }, "frameworks": { "dnx451": { diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectHandler.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectHandler.cs index ea5f844bac..1c044332cc 100644 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectHandler.cs +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectHandler.cs @@ -320,7 +320,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect // response_mode=query (explicit or not) and a response_type containing id_token // or token are not considered as a safe combination and MUST be rejected. // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security - if (!string.IsNullOrEmpty(message.IdToken) || !string.IsNullOrEmpty(message.Token)) + if (!string.IsNullOrEmpty(message.IdToken) || !string.IsNullOrEmpty(message.AccessToken)) { Logger.LogError("An OpenID Connect response cannot contain an identity token " + "or an access token when using response_mode=query"); @@ -359,7 +359,8 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect // Fail if state is missing, it's required for the correlation id. if (string.IsNullOrEmpty(message.State)) { - Logger.LogError(Resources.OIDCH_0004_MessageStateIsNullOrEmpty); + // This wasn't a valid ODIC message, it may not have been intended for us. + Logger.LogVerbose(Resources.OIDCH_0004_MessageStateIsNullOrEmpty); return null; } @@ -462,6 +463,12 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect AuthenticationTicket ticket = null; JwtSecurityToken jwt = null; + Options.ProtocolValidator.ValidateAuthenticationResponse(new OpenIdConnectProtocolValidationContext() + { + ClientId = Options.ClientId, + ProtocolMessage = message, + }); + var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(message, properties, ticket, jwt); if (authorizationCodeReceivedContext.HandledResponse) { @@ -498,7 +505,19 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect ticket = ValidateToken(tokenEndpointResponse.ProtocolMessage.IdToken, message, properties, validationParameters, out jwt); - ValidateOpenIdConnectProtocol(null, message); + var nonce = jwt?.Payload.Nonce; + if (!string.IsNullOrEmpty(nonce)) + { + nonce = ReadNonceCookie(nonce); + } + + Options.ProtocolValidator.ValidateTokenResponse(new OpenIdConnectProtocolValidationContext() + { + ClientId = Options.ClientId, + ProtocolMessage = tokenEndpointResponse.ProtocolMessage, + ValidatedIdToken = jwt, + Nonce = nonce + }); var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(message, ticket, tokenEndpointResponse); if (authenticationValidatedContext.HandledResponse) @@ -520,7 +539,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect if (Options.GetClaimsFromUserInfoEndpoint) { Logger.LogDebug(Resources.OIDCH_0040_Sending_Request_UIEndpoint); - ticket = await GetUserInformationAsync(tokenEndpointResponse.ProtocolMessage, ticket); + ticket = await GetUserInformationAsync(tokenEndpointResponse.ProtocolMessage, jwt, ticket); } return ticket; @@ -535,7 +554,19 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect var validationParameters = Options.TokenValidationParameters.Clone(); var ticket = ValidateToken(message.IdToken, message, properties, validationParameters, out jwt); - ValidateOpenIdConnectProtocol(jwt, message); + var nonce = jwt?.Payload.Nonce; + if (!string.IsNullOrEmpty(nonce)) + { + nonce = ReadNonceCookie(nonce); + } + + Options.ProtocolValidator.ValidateAuthenticationResponse(new OpenIdConnectProtocolValidationContext() + { + ClientId = Options.ClientId, + ProtocolMessage = message, + ValidatedIdToken = jwt, + Nonce = nonce + }); var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(message, ticket, tokenEndpointResponse: null); if (authenticationValidatedContext.HandledResponse) @@ -619,7 +650,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect /// message that is being processed /// authentication ticket with claims principal and identities /// Authentication ticket with identity with additional claims, if any. - protected virtual async Task GetUserInformationAsync(OpenIdConnectMessage message, AuthenticationTicket ticket) + protected virtual async Task GetUserInformationAsync(OpenIdConnectMessage message, JwtSecurityToken jwt, AuthenticationTicket ticket) { var userInfoEndpoint = _configuration?.UserInfoEndpoint; @@ -634,6 +665,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect var responseMessage = await Backchannel.SendAsync(requestMessage); responseMessage.EnsureSuccessStatusCode(); var userInfoResponse = await responseMessage.Content.ReadAsStringAsync(); + var userInfoEndpointJwt = new JwtSecurityToken(userInfoResponse); var user = JObject.Parse(userInfoResponse); var userInformationReceivedContext = await RunUserInformationReceivedEventAsync(ticket, message, user); @@ -648,20 +680,13 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect ticket = userInformationReceivedContext.AuthenticationTicket; user = userInformationReceivedContext.User; + Options.ProtocolValidator.ValidateUserInfoResponse(new OpenIdConnectProtocolValidationContext() + { + UserInfoEndpointResponse = userInfoEndpointJwt, + ValidatedIdToken = jwt, + }); + var identity = (ClaimsIdentity)ticket.Principal.Identity; - var subjectClaimType = identity.FindFirst(ClaimTypes.NameIdentifier); - if (subjectClaimType == null) - { - throw new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0041_Subject_Claim_Not_Found, identity.ToString())); - } - - var userInfoSubjectClaimValue = user.Value(JwtRegisteredClaimNames.Sub); - - // check if the sub claim matches - if (userInfoSubjectClaimValue == null || !string.Equals(userInfoSubjectClaimValue, subjectClaimType.Value, StringComparison.Ordinal)) - { - throw new OpenIdConnectProtocolException(Resources.OIDCH_0039_Subject_Claim_Mismatch); - } foreach (var claim in identity.Claims) { @@ -1097,25 +1122,6 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect return ticket; } - private void ValidateOpenIdConnectProtocol(JwtSecurityToken jwt, OpenIdConnectMessage message) - { - string nonce = jwt?.Payload.Nonce; - if (!string.IsNullOrEmpty(nonce)) - { - nonce = ReadNonceCookie(nonce); - } - - var protocolValidationContext = new OpenIdConnectProtocolValidationContext - { - ProtocolMessage = message, - IdToken = jwt, - ClientId = Options.ClientId, - Nonce = nonce - }; - - Options.ProtocolValidator.Validate(protocolValidationContext); - } - /// /// Calls InvokeReplyPathAsync /// diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectOptions.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectOptions.cs index fedc2db7be..aa77d09040 100644 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectOptions.cs +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectOptions.cs @@ -145,7 +145,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect /// if 'value' is null. public OpenIdConnectProtocolValidator ProtocolValidator { get; set; } = new OpenIdConnectProtocolValidator() { - RequireState = false, + RequireStateValidation = false, NonceLifetime = TimeSpan.FromMinutes(15) }; diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/project.json b/src/Microsoft.AspNet.Authentication.OpenIdConnect/project.json index c81a8d085b..15fe781773 100644 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/project.json +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/project.json @@ -8,7 +8,7 @@ "dependencies": { "Microsoft.AspNet.Authentication": "1.0.0-*", "Microsoft.Framework.NotNullAttribute.Sources": { "type": "build", "version": "1.0.0-*" }, - "Microsoft.IdentityModel.Protocols.OpenIdConnect": "2.0.0-beta7-*" + "Microsoft.IdentityModel.Protocols.OpenIdConnect": "2.0.0-beta8-*" }, "frameworks": { "dnx451": { diff --git a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectHandlerForTestingAuthenticate.cs b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectHandlerForTestingAuthenticate.cs index f9cd88d57e..79e96d7d1c 100644 --- a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectHandlerForTestingAuthenticate.cs +++ b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectHandlerForTestingAuthenticate.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNet.Authentication.OpenIdConnect; @@ -33,7 +34,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect return Task.FromResult(new OpenIdConnectTokenEndpointResponse(jsonResponse)); } - protected override Task GetUserInformationAsync(OpenIdConnectMessage message, AuthenticationTicket ticket) + protected override Task GetUserInformationAsync(OpenIdConnectMessage message, JwtSecurityToken jwt, AuthenticationTicket ticket) { var claimsIdentity = (ClaimsIdentity)ticket.Principal.Identity; if (claimsIdentity == null)