diff --git a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationOptions.cs b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationOptions.cs index 3dda28812..bd9ab43b0 100644 --- a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationOptions.cs +++ b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationOptions.cs @@ -99,7 +99,10 @@ public string Caption public PathString CallbackPath { get; set; } /// - /// Gets or sets the name of another authentication middleware which will be responsible for actually issuing a user . + /// Gets or sets the authentication scheme corresponding to the middleware + /// responsible of persisting user's identity after a successful authentication. + /// This value typically corresponds to a cookie middleware registered in the Startup class. + /// When omitted, is used as a fallback value. /// public string SignInScheme { get; set; } diff --git a/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationHandler.cs index e2a56697d..1f2d20b76 100644 --- a/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationHandler.cs @@ -136,12 +136,13 @@ protected override async Task AuthenticateCoreAsync() AuthenticationTicket = ticket }; - if (securityTokenReceivedNotification.HandledResponse) + await Options.Notifications.SecurityTokenValidated(securityTokenValidatedNotification); + if (securityTokenValidatedNotification.HandledResponse) { return securityTokenValidatedNotification.AuthenticationTicket; } - if (securityTokenReceivedNotification.Skipped) + if (securityTokenValidatedNotification.Skipped) { return null; } diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenidConnectAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationHandler.cs similarity index 99% rename from src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenidConnectAuthenticationHandler.cs rename to src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationHandler.cs index 6a629045b..32f7a69c0 100644 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenidConnectAuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationHandler.cs @@ -585,4 +585,4 @@ private async Task InvokeReplyPathAsync() return false; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs index 53284a50e..e58b7fff8 100644 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs @@ -7,12 +7,12 @@ using System.IdentityModel.Tokens; using System.Net.Http; using System.Text; -using Microsoft.AspNet.Builder; -using Microsoft.AspNet.DataProtection; -using Microsoft.AspNet.Http; using Microsoft.AspNet.Authentication.DataHandler; using Microsoft.AspNet.Authentication.DataHandler.Encoder; using Microsoft.AspNet.Authentication.DataHandler.Serializer; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.DataProtection; +using Microsoft.AspNet.Http; using Microsoft.Framework.Logging; using Microsoft.Framework.OptionsModel; using Microsoft.IdentityModel.Protocols; @@ -45,9 +45,14 @@ public OpenIdConnectAuthenticationMiddleware( { _logger = loggerFactory.CreateLogger(); + if (string.IsNullOrEmpty(Options.SignInScheme)) + { + Options.SignInScheme = externalOptions.Options.SignInScheme; + } + if (string.IsNullOrWhiteSpace(Options.TokenValidationParameters.AuthenticationType)) { - Options.TokenValidationParameters.AuthenticationType = externalOptions.Options.SignInScheme; + Options.TokenValidationParameters.AuthenticationType = Options.AuthenticationScheme; } if (Options.StateDataFormat == null) diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationOptions.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationOptions.cs index 25b70473a..f7bfde804 100644 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationOptions.cs +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationOptions.cs @@ -233,13 +233,12 @@ public OpenIdConnectProtocolValidator ProtocolValidator public string Scope { get; set; } /// - /// Gets or sets the AuthenticationScheme used when creating the . + /// Gets or sets the authentication scheme corresponding to the middleware + /// responsible of persisting user's identity after a successful authentication. + /// This value typically corresponds to a cookie middleware registered in the Startup class. + /// When omitted, is used as a fallback value. /// - public string SignInScheme - { - get { return TokenValidationParameters.AuthenticationType; } - set { TokenValidationParameters.AuthenticationType = value; } - } + public string SignInScheme { get; set; } /// /// Gets or sets the type used to secure data handled by the middleware. diff --git a/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationOptions.cs b/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationOptions.cs index d17daa0e9..a3179c386 100644 --- a/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationOptions.cs +++ b/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationOptions.cs @@ -89,7 +89,10 @@ public string Caption public PathString CallbackPath { get; set; } /// - /// Gets or sets the name of another authentication middleware which will be responsible for actually issuing a user . + /// Gets or sets the authentication scheme corresponding to the middleware + /// responsible of persisting user's identity after a successful authentication. + /// This value typically corresponds to a cookie middleware registered in the Startup class. + /// When omitted, is used as a fallback value. /// public string SignInScheme { get; set; } diff --git a/src/Microsoft.AspNet.Authentication/ExternalAuthenticationOptions.cs b/src/Microsoft.AspNet.Authentication/ExternalAuthenticationOptions.cs index 966e2cfe1..a80b80b83 100644 --- a/src/Microsoft.AspNet.Authentication/ExternalAuthenticationOptions.cs +++ b/src/Microsoft.AspNet.Authentication/ExternalAuthenticationOptions.cs @@ -8,6 +8,11 @@ namespace Microsoft.AspNet.Authentication { public class ExternalAuthenticationOptions { + /// + /// Gets or sets the authentication scheme corresponding to the default middleware + /// responsible of persisting user's identity after a successful authentication. + /// This value typically corresponds to a cookie middleware registered in the Startup class. + /// public string SignInScheme { get; set; } } } diff --git a/test/Microsoft.AspNet.Authentication.Test/OAuthBearer/OAuthBearerMiddlewareTests.cs b/test/Microsoft.AspNet.Authentication.Test/OAuthBearer/OAuthBearerMiddlewareTests.cs index fcd2d411e..e688497d9 100644 --- a/test/Microsoft.AspNet.Authentication.Test/OAuthBearer/OAuthBearerMiddlewareTests.cs +++ b/test/Microsoft.AspNet.Authentication.Test/OAuthBearer/OAuthBearerMiddlewareTests.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IdentityModel.Tokens; -using System.Linq; using System.Net; using System.Net.Http; using System.Security.Claims; @@ -12,7 +11,7 @@ using System.Xml.Linq; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; -using Microsoft.AspNet.Authentication.Notifications; +using Microsoft.AspNet.Http.Authentication; using Microsoft.AspNet.TestHost; using Microsoft.Framework.DependencyInjection; using Shouldly; @@ -27,6 +26,8 @@ public async Task BearerTokenValidation() { var server = CreateServer(options => { + options.AutomaticAuthentication = true; + options.Authority = "https://login.windows.net/tushartest.onmicrosoft.com"; options.Audience = "https://TusharTest.onmicrosoft.com/TodoListService-ManualJwt"; options.TokenValidationParameters = new TokenValidationParameters @@ -34,6 +35,7 @@ public async Task BearerTokenValidation() ValidateLifetime = false }; }); + string newBearerToken = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImtyaU1QZG1Cdng2OHNrVDgtbVBBQjNCc2VlQSJ9.eyJhdWQiOiJodHRwczovL1R1c2hhclRlc3Qub25taWNyb3NvZnQuY29tL1RvZG9MaXN0U2VydmljZS1NYW51YWxKd3QiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9hZmJlY2UwMy1hZWFhLTRmM2YtODVlNy1jZTA4ZGQyMGNlNTAvIiwiaWF0IjoxNDE4MzMwNjE0LCJuYmYiOjE0MTgzMzA2MTQsImV4cCI6MTQxODMzNDUxNCwidmVyIjoiMS4wIiwidGlkIjoiYWZiZWNlMDMtYWVhYS00ZjNmLTg1ZTctY2UwOGRkMjBjZTUwIiwiYW1yIjpbInB3ZCJdLCJvaWQiOiI1Mzk3OTdjMi00MDE5LTQ2NTktOWRiNS03MmM0Yzc3NzhhMzMiLCJ1cG4iOiJWaWN0b3JAVHVzaGFyVGVzdC5vbm1pY3Jvc29mdC5jb20iLCJ1bmlxdWVfbmFtZSI6IlZpY3RvckBUdXNoYXJUZXN0Lm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IkQyMm9aMW9VTzEzTUFiQXZrdnFyd2REVE80WXZJdjlzMV9GNWlVOVUwYnciLCJmYW1pbHlfbmFtZSI6Ikd1cHRhIiwiZ2l2ZW5fbmFtZSI6IlZpY3RvciIsImFwcGlkIjoiNjEzYjVhZjgtZjJjMy00MWI2LWExZGMtNDE2Yzk3ODAzMGI3IiwiYXBwaWRhY3IiOiIwIiwic2NwIjoidXNlcl9pbXBlcnNvbmF0aW9uIiwiYWNyIjoiMSJ9.N_Kw1EhoVGrHbE6hOcm7ERdZ7paBQiNdObvp2c6T6n5CE8p0fZqmUd-ya_EqwElcD6SiKSiP7gj0gpNUnOJcBl_H2X8GseaeeMxBrZdsnDL8qecc6_ygHruwlPltnLTdka67s1Ow4fDSHaqhVTEk6lzGmNEcbNAyb0CxQxU6o7Fh0yHRiWoLsT8yqYk8nKzsHXfZBNby4aRo3_hXaa4i0SZLYfDGGYPdttG4vT_u54QGGd4Wzbonv2gjDlllOVGOwoJS6kfl1h8mk0qxdiIaT_ChbDWgkWvTB7bTvBE-EgHgV0XmAo0WtJeSxgjsG3KhhEPsONmqrSjhIUV4IVnF2w"; var response = await SendAsync(server, "http://example.com/oauth", newBearerToken); response.Response.StatusCode.ShouldBe(HttpStatusCode.OK); @@ -44,26 +46,30 @@ public async Task CustomHeaderReceived() { var server = CreateServer(options => { - options.Notifications.MessageReceived = HeaderReceived; - }); - - var response = await SendAsync(server, "http://example.com/oauth", "someHeader someblob"); - response.Response.StatusCode.ShouldBe(HttpStatusCode.OK); - } + options.AutomaticAuthentication = true; - private static Task HeaderReceived(MessageReceivedNotification notification) - { - List claims = - new List + options.Notifications.MessageReceived = notification => { - new Claim(ClaimTypes.Email, "bob@contoso.com"), - new Claim(ClaimsIdentity.DefaultNameClaimType, "bob"), - }; + var claims = new[] + { + new Claim(ClaimTypes.NameIdentifier, "Bob le Magnifique"), + new Claim(ClaimTypes.Email, "bob@contoso.com"), + new Claim(ClaimsIdentity.DefaultNameClaimType, "bob") + }; - notification.AuthenticationTicket = new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims)), new Http.Authentication.AuthenticationProperties(), notification.Options.AuthenticationScheme); - notification.HandleResponse(); + notification.AuthenticationTicket = new AuthenticationTicket( + new ClaimsPrincipal(new ClaimsIdentity(claims, notification.Options.AuthenticationScheme)), + new AuthenticationProperties(), notification.Options.AuthenticationScheme); - return Task.FromResult(null); + notification.HandleResponse(); + + return Task.FromResult(null); + }; + }); + + var response = await SendAsync(server, "http://example.com/oauth", "someHeader someblob"); + response.Response.StatusCode.ShouldBe(HttpStatusCode.OK); + response.ResponseText.ShouldBe("Bob le Magnifique"); } [Fact] @@ -71,7 +77,7 @@ public async Task NoHeaderReceived() { var server = CreateServer(options => { }); var response = await SendAsync(server, "http://example.com/oauth"); - response.Response.StatusCode.ShouldBe(HttpStatusCode.OK); + response.Response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); } [Fact] @@ -79,7 +85,7 @@ public async Task HeaderWithoutBearerReceived() { var server = CreateServer(options => { }); var response = await SendAsync(server, "http://example.com/oauth","Token"); - response.Response.StatusCode.ShouldBe(HttpStatusCode.OK); + response.Response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); } [Fact] @@ -87,26 +93,30 @@ public async Task CustomTokenReceived() { var server = CreateServer(options => { - options.Notifications.SecurityTokenReceived = SecurityTokenReceived; - }); - - var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob"); - response.Response.StatusCode.ShouldBe(HttpStatusCode.OK); - } + options.AutomaticAuthentication = true; - private static Task SecurityTokenReceived(SecurityTokenReceivedNotification notification) - { - List claims = - new List + options.Notifications.SecurityTokenReceived = notification => { - new Claim(ClaimTypes.Email, "bob@contoso.com"), - new Claim(ClaimsIdentity.DefaultNameClaimType, "bob"), - }; + var claims = new[] + { + new Claim(ClaimTypes.NameIdentifier, "Bob le Magnifique"), + new Claim(ClaimTypes.Email, "bob@contoso.com"), + new Claim(ClaimsIdentity.DefaultNameClaimType, "bob") + }; - notification.AuthenticationTicket = new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, notification.Options.AuthenticationScheme)), new Http.Authentication.AuthenticationProperties(), notification.Options.AuthenticationScheme); - notification.HandleResponse(); + notification.AuthenticationTicket = new AuthenticationTicket( + new ClaimsPrincipal(new ClaimsIdentity(claims, notification.Options.AuthenticationScheme)), + new AuthenticationProperties(), notification.Options.AuthenticationScheme); - return Task.FromResult(null); + notification.HandleResponse(); + + return Task.FromResult(null); + }; + }); + + var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob"); + response.Response.StatusCode.ShouldBe(HttpStatusCode.OK); + response.ResponseText.ShouldBe("Bob le Magnifique"); } [Fact] @@ -114,44 +124,70 @@ public async Task CustomTokenValidated() { var server = CreateServer(options => { - options.Notifications.SecurityTokenValidated = SecurityTokenValidated; - options.SecurityTokenValidators = new List{new BlobTokenValidator(options.AuthenticationScheme)}; + options.AutomaticAuthentication = true; + + options.Notifications.SecurityTokenValidated = notification => + { + // Retrieve the NameIdentifier claim from the identity + // returned by the custom security token validator. + var identity = (ClaimsIdentity) notification.AuthenticationTicket.Principal.Identity; + var identifier = identity.FindFirst(ClaimTypes.NameIdentifier); + + identifier.Value.ShouldBe("Bob le Tout Puissant"); + + // Remove the existing NameIdentifier claim and replace it + // with a new one containing a different value. + identity.RemoveClaim(identifier); + // Make sure to use a different name identifier + // than the one defined by BlobTokenValidator. + identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "Bob le Magnifique")); + + return Task.FromResult(null); + }; + + options.SecurityTokenValidators = new[] { new BlobTokenValidator(options.AuthenticationScheme) }; }); var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob"); response.Response.StatusCode.ShouldBe(HttpStatusCode.OK); + response.ResponseText.ShouldBe("Bob le Magnifique"); } - private static Task SecurityTokenValidated(SecurityTokenValidatedNotification notification) + [Fact] + public async Task RetrievingTokenFromAlternateLocation() { - List claims = - new List + var server = CreateServer(options => + { + options.AutomaticAuthentication = true; + + options.Notifications.MessageReceived = notification => { - new Claim(ClaimTypes.Email, "bob@contoso.com"), - new Claim(ClaimsIdentity.DefaultNameClaimType, "bob"), + notification.Token = "CustomToken"; + return Task.FromResult(null); }; + + options.Notifications.SecurityTokenReceived = notification => + { + var claims = new[] + { + new Claim(ClaimTypes.NameIdentifier, "Bob le Magnifique"), + new Claim(ClaimTypes.Email, "bob@contoso.com"), + new Claim(ClaimsIdentity.DefaultNameClaimType, "bob") + }; - notification.AuthenticationTicket = new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, notification.Options.AuthenticationScheme)), new Http.Authentication.AuthenticationProperties(), notification.Options.AuthenticationScheme); - notification.HandleResponse(); + notification.AuthenticationTicket = new AuthenticationTicket( + new ClaimsPrincipal(new ClaimsIdentity(claims, notification.Options.AuthenticationScheme)), + new AuthenticationProperties(), notification.Options.AuthenticationScheme); - return Task.FromResult(null); - } + notification.HandleResponse(); - [Fact] - public async Task RetrievingTokenFromAlternateLocation() - { - var server = CreateServer(options => { - options.Notifications.MessageReceived = MessageReceived; - options.Notifications.SecurityTokenReceived = SecurityTokenReceived; + return Task.FromResult(null); + }; }); + var response = await SendAsync(server, "http://example.com/oauth", "Bearer Token"); response.Response.StatusCode.ShouldBe(HttpStatusCode.OK); - } - - private static Task MessageReceived(MessageReceivedNotification notification) - { - notification.Token = "CustomToken"; - return Task.FromResult(null); + response.ResponseText.ShouldBe("Bob le Magnifique"); } [Fact] @@ -159,20 +195,51 @@ public async Task BearerTurns401To403IfAuthenticated() { var server = CreateServer(options => { - options.Notifications.SecurityTokenReceived = SecurityTokenReceived; + options.Notifications.SecurityTokenReceived = notification => + { + var claims = new[] + { + new Claim(ClaimTypes.NameIdentifier, "Bob le Magnifique"), + new Claim(ClaimTypes.Email, "bob@contoso.com"), + new Claim(ClaimsIdentity.DefaultNameClaimType, "bob") + }; + + notification.AuthenticationTicket = new AuthenticationTicket( + new ClaimsPrincipal(new ClaimsIdentity(claims, notification.Options.AuthenticationScheme)), + new AuthenticationProperties(), notification.Options.AuthenticationScheme); + + notification.HandleResponse(); + + return Task.FromResult(null); + }; }); var response = await SendAsync(server, "http://example.com/unauthorized", "Bearer Token"); response.Response.StatusCode.ShouldBe(HttpStatusCode.Forbidden); } - - + [Fact] public async Task BearerDoesNothingTo401IfNotAuthenticated() { var server = CreateServer(options => { - options.Notifications.SecurityTokenReceived = SecurityTokenReceived; + options.Notifications.SecurityTokenReceived = notification => + { + var claims = new[] + { + new Claim(ClaimTypes.NameIdentifier, "Bob le Magnifique"), + new Claim(ClaimTypes.Email, "bob@contoso.com"), + new Claim(ClaimsIdentity.DefaultNameClaimType, "bob") + }; + + notification.AuthenticationTicket = new AuthenticationTicket( + new ClaimsPrincipal(new ClaimsIdentity(claims, notification.Options.AuthenticationScheme)), + new AuthenticationProperties(), notification.Options.AuthenticationScheme); + + notification.HandleResponse(); + + return Task.FromResult(null); + }; }); var response = await SendAsync(server, "http://example.com/unauthorized"); @@ -187,21 +254,15 @@ public BlobTokenValidator(string authenticationScheme) AuthenticationScheme = authenticationScheme; } - public string AuthenticationScheme { get; set; } - - public bool CanValidateToken - { - get - { - return true; - } - } + public string AuthenticationScheme { get; } + public bool CanValidateToken => true; + public int MaximumTokenSizeInBytes { get { - return 2*2*1024; + throw new NotImplementedException(); } set @@ -210,20 +271,20 @@ public int MaximumTokenSizeInBytes } } - public bool CanReadToken(string securityToken) - { - return true; - } + public bool CanReadToken(string securityToken) => true; public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken) { validatedToken = null; - List claims = - new List - { - new Claim(ClaimTypes.Email, "bob@contoso.com"), - new Claim(ClaimsIdentity.DefaultNameClaimType, "bob"), - }; + + var claims = new[] + { + // Make sure to use a different name identifier + // than the one defined by CustomTokenValidated. + new Claim(ClaimTypes.NameIdentifier, "Bob le Tout Puissant"), + new Claim(ClaimTypes.Email, "bob@contoso.com"), + new Claim(ClaimsIdentity.DefaultNameClaimType, "bob"), + }; return new ClaimsPrincipal(new ClaimsIdentity(claims, AuthenticationScheme)); } @@ -237,24 +298,42 @@ private static TestServer CreateServer(Action { app.UseOAuthBearerAuthentication(configureOptions); } + app.Use(async (context, next) => { - var req = context.Request; - var res = context.Response; - if (req.Path == new PathString("/oauth")) + if (context.Request.Path == new PathString("/oauth")) { + if (context.User == null || + context.User.Identity == null || + !context.User.Identity.IsAuthenticated) + { + context.Response.StatusCode = 401; + + return; + } + + var identifier = context.User.FindFirst(ClaimTypes.NameIdentifier); + if (identifier == null) + { + context.Response.StatusCode = 500; + + return; + } + + await context.Response.WriteAsync(identifier.Value); } - else if (req.Path == new PathString("/unauthorized")) + + else if (context.Request.Path == new PathString("/unauthorized")) { // Simulate Authorization failure var result = await context.AuthenticateAsync(OAuthBearerAuthenticationDefaults.AuthenticationScheme); - res.Challenge(OAuthBearerAuthenticationDefaults.AuthenticationScheme); + context.Response.Challenge(OAuthBearerAuthenticationDefaults.AuthenticationScheme); } + else { await next(); } - }); }, services => services.AddDataProtection());