diff --git a/samples/CookieSessionSample/CookieSessionSample.xproj b/samples/CookieSessionSample/CookieSessionSample.xproj
index 1216341876..e129ba553c 100644
--- a/samples/CookieSessionSample/CookieSessionSample.xproj
+++ b/samples/CookieSessionSample/CookieSessionSample.xproj
@@ -12,7 +12,7 @@
2.0
- 22571
+ 36505
\ No newline at end of file
diff --git a/samples/SocialSample/SocialSample.xproj b/samples/SocialSample/SocialSample.xproj
index ce1e69ddf4..74aaf1222f 100644
--- a/samples/SocialSample/SocialSample.xproj
+++ b/samples/SocialSample/SocialSample.xproj
@@ -12,7 +12,7 @@
2.0
- 22570
+ 36504
\ No newline at end of file
diff --git a/samples/SocialSample/Startup.cs b/samples/SocialSample/Startup.cs
index dab45953da..bc7d61b4ae 100644
--- a/samples/SocialSample/Startup.cs
+++ b/samples/SocialSample/Startup.cs
@@ -1,6 +1,7 @@
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
+using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.DataProtection;
using Microsoft.AspNet.Http;
@@ -28,6 +29,13 @@ namespace CookieSample
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
});
+ services.ConfigureClaimsTransformation(p =>
+ {
+ var id = new ClaimsIdentity("xform");
+ id.AddClaim(new Claim("ClaimsTransformation", "TransformAddedClaim"));
+ p.AddIdentity(id);
+ return p;
+ });
});
app.UseCookieAuthentication(options =>
diff --git a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs
index 808a2691a2..8ccbb36530 100644
--- a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs
+++ b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs
@@ -13,7 +13,7 @@ using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Authentication.Cookies
{
- internal class CookieAuthenticationHandler : AutomaticAuthenticationHandler
+ internal class CookieAuthenticationHandler : AuthenticationHandler
{
private const string HeaderNameCacheControl = "Cache-Control";
private const string HeaderNamePragma = "Pragma";
diff --git a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationMiddleware.cs
index e3ef3f5700..cd6b53b39e 100644
--- a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationMiddleware.cs
+++ b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationMiddleware.cs
@@ -15,11 +15,12 @@ namespace Microsoft.AspNet.Authentication.Cookies
{
private readonly ILogger _logger;
- public CookieAuthenticationMiddleware(RequestDelegate next,
- IServiceProvider services,
- IDataProtectionProvider dataProtectionProvider,
- ILoggerFactory loggerFactory,
- IOptions options,
+ public CookieAuthenticationMiddleware(
+ [NotNull] RequestDelegate next,
+ [NotNull] IServiceProvider services,
+ [NotNull] IDataProtectionProvider dataProtectionProvider,
+ [NotNull] ILoggerFactory loggerFactory,
+ [NotNull] IOptions options,
ConfigureOptions configureOptions)
: base(next, services, options, configureOptions)
{
diff --git a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationOptions.cs b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationOptions.cs
index 02e9743b4a..c2fc4c3a71 100644
--- a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationOptions.cs
+++ b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationOptions.cs
@@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
///
/// Contains the options used by the CookiesAuthenticationMiddleware
///
- public class CookieAuthenticationOptions : AutomaticAuthenticationOptions
+ public class CookieAuthenticationOptions : AuthenticationOptions
{
private string _cookieName;
@@ -20,7 +20,6 @@ namespace Microsoft.AspNet.Authentication.Cookies
///
public CookieAuthenticationOptions()
{
- AutomaticAuthentication = true;
AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme;
ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
ExpireTimeSpan = TimeSpan.FromDays(14);
diff --git a/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationMiddleware.cs
index b74a5be1dd..506453c54c 100644
--- a/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationMiddleware.cs
+++ b/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationMiddleware.cs
@@ -25,12 +25,12 @@ namespace Microsoft.AspNet.Authentication.Facebook
///
/// Configuration options for the middleware.
public FacebookAuthenticationMiddleware(
- RequestDelegate next,
- IServiceProvider services,
- IDataProtectionProvider dataProtectionProvider,
- ILoggerFactory loggerFactory,
- IOptions externalOptions,
- IOptions options,
+ [NotNull] RequestDelegate next,
+ [NotNull] IServiceProvider services,
+ [NotNull] IDataProtectionProvider dataProtectionProvider,
+ [NotNull] ILoggerFactory loggerFactory,
+ [NotNull] IOptions externalOptions,
+ [NotNull] IOptions options,
ConfigureOptions configureOptions = null)
: base(next, services, dataProtectionProvider, loggerFactory, externalOptions, options, configureOptions)
{
diff --git a/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationMiddleware.cs
index 0da3f49284..819642a852 100644
--- a/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationMiddleware.cs
+++ b/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationMiddleware.cs
@@ -30,12 +30,12 @@ namespace Microsoft.AspNet.Authentication.Google
///
/// Configuration options for the middleware.
public GoogleAuthenticationMiddleware(
- RequestDelegate next,
- IServiceProvider services,
- IDataProtectionProvider dataProtectionProvider,
- ILoggerFactory loggerFactory,
- IOptions externalOptions,
- IOptions options,
+ [NotNull] RequestDelegate next,
+ [NotNull] IServiceProvider services,
+ [NotNull] IDataProtectionProvider dataProtectionProvider,
+ [NotNull] ILoggerFactory loggerFactory,
+ [NotNull] IOptions externalOptions,
+ [NotNull] IOptions options,
ConfigureOptions configureOptions = null)
: base(next, services, dataProtectionProvider, loggerFactory, externalOptions, options, configureOptions)
{
diff --git a/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationMiddleware.cs
index ac2ebc1848..f0430bd724 100644
--- a/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationMiddleware.cs
+++ b/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationMiddleware.cs
@@ -26,12 +26,12 @@ namespace Microsoft.AspNet.Authentication.MicrosoftAccount
///
/// Configuration options for the middleware.
public MicrosoftAccountAuthenticationMiddleware(
- RequestDelegate next,
- IServiceProvider services,
- IDataProtectionProvider dataProtectionProvider,
- ILoggerFactory loggerFactory,
- IOptions externalOptions,
- IOptions options,
+ [NotNull] RequestDelegate next,
+ [NotNull] IServiceProvider services,
+ [NotNull] IDataProtectionProvider dataProtectionProvider,
+ [NotNull] ILoggerFactory loggerFactory,
+ [NotNull] IOptions externalOptions,
+ [NotNull] IOptions options,
ConfigureOptions configureOptions = null)
: base(next, services, dataProtectionProvider, loggerFactory, externalOptions, options, configureOptions)
{
diff --git a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationHandler.cs
index 6a51d85e97..b8696a302b 100644
--- a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationHandler.cs
+++ b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationHandler.cs
@@ -176,13 +176,19 @@ namespace Microsoft.AspNet.Authentication.OAuth
protected override void ApplyResponseChallenge()
{
+ if (ShouldConvertChallengeToForbidden())
+ {
+ Response.StatusCode = 403;
+ return;
+ }
+
if (Response.StatusCode != 401)
{
return;
}
- // Only redirect on challenges
- if (ChallengeContext == null)
+ // When Automatic should redirect on 401 even if there wasn't an explicit challenge.
+ if (ChallengeContext == null && !Options.AutomaticAuthentication)
{
return;
}
diff --git a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationMiddleware.cs
index 0d272bf1ea..1c96be1e49 100644
--- a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationMiddleware.cs
+++ b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationMiddleware.cs
@@ -31,12 +31,12 @@ namespace Microsoft.AspNet.Authentication.OAuth
///
/// Configuration options for the middleware.
public OAuthAuthenticationMiddleware(
- RequestDelegate next,
- IServiceProvider services,
- IDataProtectionProvider dataProtectionProvider,
- ILoggerFactory loggerFactory,
- IOptions externalOptions,
- IOptions options,
+ [NotNull] RequestDelegate next,
+ [NotNull] IServiceProvider services,
+ [NotNull] IDataProtectionProvider dataProtectionProvider,
+ [NotNull] ILoggerFactory loggerFactory,
+ [NotNull] IOptions externalOptions,
+ [NotNull] IOptions options,
ConfigureOptions configureOptions = null)
: base(next, services, options, configureOptions)
{
diff --git a/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationHandler.cs
index 7f41be810c..e2a56697d9 100644
--- a/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationHandler.cs
+++ b/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationHandler.cs
@@ -15,7 +15,7 @@ using Microsoft.IdentityModel.Protocols;
namespace Microsoft.AspNet.Authentication.OAuthBearer
{
- public class OAuthBearerAuthenticationHandler : AutomaticAuthenticationHandler
+ public class OAuthBearerAuthenticationHandler : AuthenticationHandler
{
private readonly ILogger _logger;
private OpenIdConnectConfiguration _configuration;
@@ -197,7 +197,7 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
return;
}
- if ((Response.StatusCode != 401) || (ChallengeContext == null))
+ if ((Response.StatusCode != 401) || (ChallengeContext == null && !Options.AutomaticAuthentication))
{
return;
}
diff --git a/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationMiddleware.cs
index 4083af2393..9b47e5c36e 100644
--- a/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationMiddleware.cs
+++ b/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationMiddleware.cs
@@ -29,10 +29,10 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
/// extension method.
///
public OAuthBearerAuthenticationMiddleware(
- RequestDelegate next,
- IServiceProvider services,
- ILoggerFactory loggerFactory,
- IOptions options,
+ [NotNull] RequestDelegate next,
+ [NotNull] IServiceProvider services,
+ [NotNull] ILoggerFactory loggerFactory,
+ [NotNull] IOptions options,
ConfigureOptions configureOptions)
: base(next, services, options, configureOptions)
{
diff --git a/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationOptions.cs b/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationOptions.cs
index ab0b833008..9bbc6300c5 100644
--- a/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationOptions.cs
+++ b/src/Microsoft.AspNet.Authentication.OAuthBearer/OAuthBearerAuthenticationOptions.cs
@@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Authentication.OAuthBearer
///
/// Options class provides information needed to control Bearer Authentication middleware behavior
///
- public class OAuthBearerAuthenticationOptions : AutomaticAuthenticationOptions
+ public class OAuthBearerAuthenticationOptions : AuthenticationOptions
{
private ICollection _securityTokenValidators;
private TokenValidationParameters _tokenValidationParameters;
diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs
index c316c85837..1f4848140d 100644
--- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs
+++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs
@@ -35,12 +35,12 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// Configuration options for the middleware
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Managed by caller")]
public OpenIdConnectAuthenticationMiddleware(
- RequestDelegate next,
- IServiceProvider services,
- IDataProtectionProvider dataProtectionProvider,
- ILoggerFactory loggerFactory,
- IOptions externalOptions,
- IOptions options,
+ [NotNull] RequestDelegate next,
+ [NotNull] IServiceProvider services,
+ [NotNull] IDataProtectionProvider dataProtectionProvider,
+ [NotNull] ILoggerFactory loggerFactory,
+ [NotNull] IOptions externalOptions,
+ [NotNull] IOptions options,
ConfigureOptions configureOptions)
: base(next, services, options, configureOptions)
{
diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenidConnectAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenidConnectAuthenticationHandler.cs
index 68e9bea24a..6a629045b8 100644
--- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenidConnectAuthenticationHandler.cs
+++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenidConnectAuthenticationHandler.cs
@@ -117,13 +117,19 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
///
protected override async Task ApplyResponseChallengeAsync()
{
+ if (ShouldConvertChallengeToForbidden())
+ {
+ Response.StatusCode = 403;
+ return;
+ }
+
if (Response.StatusCode != 401)
{
return;
}
- // Only redirect on challenges
- if (ChallengeContext == null)
+ // When Automatic should redirect on 401 even if there wasn't an explicit challenge.
+ if (ChallengeContext == null && !Options.AutomaticAuthentication)
{
return;
}
diff --git a/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationHandler.cs
index 355fb84b27..6f2aade300 100644
--- a/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationHandler.cs
+++ b/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationHandler.cs
@@ -131,13 +131,19 @@ namespace Microsoft.AspNet.Authentication.Twitter
protected override async Task ApplyResponseChallengeAsync()
{
+ if (ShouldConvertChallengeToForbidden())
+ {
+ Response.StatusCode = 403;
+ return;
+ }
+
if (Response.StatusCode != 401)
{
return;
}
- // Only redirect on challenges
- if (ChallengeContext == null)
+ // When Automatic should redirect on 401 even if there wasn't an explicit challenge.
+ if (ChallengeContext == null && !Options.AutomaticAuthentication)
{
return;
}
diff --git a/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationMiddleware.cs
index 4ffedd9924..11ae3b4bdc 100644
--- a/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationMiddleware.cs
+++ b/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationMiddleware.cs
@@ -33,12 +33,12 @@ namespace Microsoft.AspNet.Authentication.Twitter
///
/// Configuration options for the middleware
public TwitterAuthenticationMiddleware(
- RequestDelegate next,
- IServiceProvider services,
- IDataProtectionProvider dataProtectionProvider,
- ILoggerFactory loggerFactory,
- IOptions externalOptions,
- IOptions options,
+ [NotNull] RequestDelegate next,
+ [NotNull] IServiceProvider services,
+ [NotNull] IDataProtectionProvider dataProtectionProvider,
+ [NotNull] ILoggerFactory loggerFactory,
+ [NotNull] IOptions externalOptions,
+ [NotNull] IOptions options,
ConfigureOptions configureOptions = null)
: base(next, services, options, configureOptions)
{
diff --git a/src/Microsoft.AspNet.Authentication/AuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication/AuthenticationHandler.cs
index 93d1ab6298..d54c4a3bee 100644
--- a/src/Microsoft.AspNet.Authentication/AuthenticationHandler.cs
+++ b/src/Microsoft.AspNet.Authentication/AuthenticationHandler.cs
@@ -72,6 +72,15 @@ namespace Microsoft.AspNet.Authentication
Response.OnSendingHeaders(OnSendingHeaderCallback, this);
await InitializeCoreAsync();
+
+ if (BaseOptions.AutomaticAuthentication)
+ {
+ var ticket = await AuthenticateAsync();
+ if (ticket?.Principal != null)
+ {
+ SecurityHelper.AddUserPrincipal(Context, ticket.Principal);
+ }
+ }
}
private static void OnSendingHeaderCallback(object state)
@@ -128,7 +137,7 @@ namespace Microsoft.AspNet.Authentication
/// pipeline.
public virtual Task InvokeAsync()
{
- return Task.FromResult(false);
+ return Task.FromResult(false);
}
public virtual void GetDescriptions(IDescribeSchemesContext describeContext)
@@ -143,17 +152,17 @@ namespace Microsoft.AspNet.Authentication
public virtual void Authenticate(IAuthenticateContext context)
{
- if (context.AuthenticationSchemes.Contains(BaseOptions.AuthenticationScheme, StringComparer.Ordinal))
+ if (ShouldHandleScheme(context.AuthenticationScheme))
{
- AuthenticationTicket ticket = Authenticate();
- if (ticket != null && ticket.Principal != null)
+ var ticket = Authenticate();
+ if (ticket?.Principal != null)
{
AuthenticateCalled = true;
context.Authenticated(ticket.Principal, ticket.Properties.Dictionary, BaseOptions.Description.Dictionary);
}
else
{
- context.NotAuthenticated(BaseOptions.AuthenticationScheme, properties: null, description: BaseOptions.Description.Dictionary);
+ context.NotAuthenticated();
}
}
@@ -165,17 +174,17 @@ namespace Microsoft.AspNet.Authentication
public virtual async Task AuthenticateAsync(IAuthenticateContext context)
{
- if (context.AuthenticationSchemes.Contains(BaseOptions.AuthenticationScheme, StringComparer.Ordinal))
+ if (ShouldHandleScheme(context.AuthenticationScheme))
{
- AuthenticationTicket ticket = await AuthenticateAsync();
- if (ticket != null && ticket.Principal != null)
+ var ticket = await AuthenticateAsync();
+ if (ticket?.Principal != null)
{
AuthenticateCalled = true;
context.Authenticated(ticket.Principal, ticket.Properties.Dictionary, BaseOptions.Description.Dictionary);
}
else
{
- context.NotAuthenticated(BaseOptions.AuthenticationScheme, properties: null, description: BaseOptions.Description.Dictionary);
+ context.NotAuthenticated();
}
}
@@ -360,14 +369,26 @@ namespace Microsoft.AspNet.Authentication
public virtual bool ShouldHandleScheme(IEnumerable authenticationSchemes)
{
- return authenticationSchemes != null &&
- authenticationSchemes.Any() &&
- authenticationSchemes.Contains(BaseOptions.AuthenticationScheme, StringComparer.Ordinal);
+ // If there are any schemes asked for, need to match, otherwise automatic authentication matches
+ return authenticationSchemes != null && authenticationSchemes.Any()
+ ? authenticationSchemes.Contains(BaseOptions.AuthenticationScheme, StringComparer.Ordinal)
+ : BaseOptions.AutomaticAuthentication;
}
public virtual bool ShouldHandleScheme(string authenticationScheme)
{
- return string.Equals(BaseOptions.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal);
+ return string.Equals(BaseOptions.AuthenticationScheme, authenticationScheme, StringComparison.Ordinal) ||
+ (BaseOptions.AutomaticAuthentication && string.IsNullOrWhiteSpace(authenticationScheme));
+ }
+
+ public virtual bool ShouldConvertChallengeToForbidden()
+ {
+ // Return 403 iff 401 and this handler's authenticate was called
+ // and the challenge is for the authentication type
+ return Response.StatusCode == 401 &&
+ AuthenticateCalled &&
+ ChallengeContext != null &&
+ ShouldHandleScheme(ChallengeContext.AuthenticationSchemes);
}
///
@@ -384,11 +405,11 @@ namespace Microsoft.AspNet.Authentication
protected void GenerateCorrelationId([NotNull] AuthenticationProperties properties)
{
- string correlationKey = Constants.CorrelationPrefix + BaseOptions.AuthenticationScheme;
+ var correlationKey = Constants.CorrelationPrefix + BaseOptions.AuthenticationScheme;
var nonceBytes = new byte[32];
CryptoRandom.GetBytes(nonceBytes);
- string correlationId = TextEncodings.Base64Url.Encode(nonceBytes);
+ var correlationId = TextEncodings.Base64Url.Encode(nonceBytes);
var cookieOptions = new CookieOptions
{
@@ -403,9 +424,8 @@ namespace Microsoft.AspNet.Authentication
protected bool ValidateCorrelationId([NotNull] AuthenticationProperties properties, [NotNull] ILogger logger)
{
- string correlationKey = Constants.CorrelationPrefix + BaseOptions.AuthenticationScheme;
-
- string correlationCookie = Request.Cookies[correlationKey];
+ var correlationKey = Constants.CorrelationPrefix + BaseOptions.AuthenticationScheme;
+ var correlationCookie = Request.Cookies[correlationKey];
if (string.IsNullOrWhiteSpace(correlationCookie))
{
logger.LogWarning("{0} cookie not found.", correlationKey);
@@ -453,3 +473,4 @@ namespace Microsoft.AspNet.Authentication
}
}
}
+
diff --git a/src/Microsoft.AspNet.Authentication/AuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication/AuthenticationMiddleware.cs
index f78eeaa55e..a9482ed465 100644
--- a/src/Microsoft.AspNet.Authentication/AuthenticationMiddleware.cs
+++ b/src/Microsoft.AspNet.Authentication/AuthenticationMiddleware.cs
@@ -16,7 +16,11 @@ namespace Microsoft.AspNet.Authentication
private readonly RequestDelegate _next;
private readonly IServiceProvider _services;
- protected AuthenticationMiddleware([NotNull] RequestDelegate next, [NotNull] IServiceProvider services, [NotNull] IOptions options, ConfigureOptions configureOptions)
+ protected AuthenticationMiddleware(
+ [NotNull] RequestDelegate next,
+ [NotNull] IServiceProvider services,
+ [NotNull] IOptions options,
+ ConfigureOptions configureOptions)
{
if (configureOptions != null)
{
diff --git a/src/Microsoft.AspNet.Authentication/AuthenticationOptions.cs b/src/Microsoft.AspNet.Authentication/AuthenticationOptions.cs
index 09df9e5da1..c91a5bddf6 100644
--- a/src/Microsoft.AspNet.Authentication/AuthenticationOptions.cs
+++ b/src/Microsoft.AspNet.Authentication/AuthenticationOptions.cs
@@ -26,6 +26,13 @@ namespace Microsoft.AspNet.Authentication
}
}
+ ///
+ /// If true the authentication middleware alter the request user coming in and
+ /// alter 401 Unauthorized responses going out. If false the authentication middleware will only provide
+ /// identity and alter responses when explicitly indicated by the AuthenticationScheme.
+ ///
+ public bool AutomaticAuthentication { get; set; }
+
///
/// Additional information about the authentication type which is made available to the application.
///
diff --git a/src/Microsoft.AspNet.Authentication/AuthenticationServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Authentication/AuthenticationServiceCollectionExtensions.cs
new file mode 100644
index 0000000000..55b82acab6
--- /dev/null
+++ b/src/Microsoft.AspNet.Authentication/AuthenticationServiceCollectionExtensions.cs
@@ -0,0 +1,26 @@
+// 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;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Authentication;
+using Microsoft.AspNet.Http.Core.Authentication;
+using Microsoft.Framework.Internal;
+
+namespace Microsoft.Framework.DependencyInjection
+{
+ public static class AuthenticationServiceCollectionExtensions
+ {
+ public static IServiceCollection ConfigureClaimsTransformation([NotNull] this IServiceCollection services, [NotNull] Action configure)
+ {
+ return services.Configure(configure);
+ }
+
+ public static IServiceCollection ConfigureClaimsTransformation([NotNull] this IServiceCollection services, [NotNull] Func transform)
+ {
+ return services.Configure(o => o.Transformation = transform);
+ }
+
+ }
+}
diff --git a/src/Microsoft.AspNet.Authentication/AutomaticAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication/AutomaticAuthenticationHandler.cs
deleted file mode 100644
index f67f2a908d..0000000000
--- a/src/Microsoft.AspNet.Authentication/AutomaticAuthenticationHandler.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-// 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.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using Microsoft.AspNet.Http.Authentication;
-
-namespace Microsoft.AspNet.Authentication
-{
- ///
- /// Base class for the per-request work performed by automatic authentication middleware.
- ///
- /// Specifies which type for of AutomaticAuthenticationOptions property
- public abstract class AutomaticAuthenticationHandler : AuthenticationHandler where TOptions : AutomaticAuthenticationOptions
- {
- public virtual bool ShouldConvertChallengeToForbidden()
- {
- // Return 403 iff 401 and this handler's authenticate was called
- // and the challenge is for the authentication type
- return Response.StatusCode == 401 &&
- AuthenticateCalled &&
- ChallengeContext != null &&
- ShouldHandleScheme(ChallengeContext.AuthenticationSchemes);
- }
-
- protected async override Task InitializeCoreAsync()
- {
- if (Options.AutomaticAuthentication)
- {
- AuthenticationTicket ticket = await AuthenticateAsync();
- if (ticket != null && ticket.Principal != null)
- {
- SecurityHelper.AddUserPrincipal(Context, ticket.Principal);
- }
- }
- }
-
- public override void SignOut(ISignOutContext context)
- {
- // Empty or null auth scheme is allowed for automatic Authentication
- if (Options.AutomaticAuthentication && string.IsNullOrWhiteSpace(context.AuthenticationScheme))
- {
- SignInContext = null;
- SignOutContext = context;
- context.Accept();
- }
-
- base.SignOut(context);
- }
-
- public override void Challenge(IChallengeContext context)
- {
- // Null or Empty scheme allowed for automatic authentication
- if (Options.AutomaticAuthentication &&
- (context.AuthenticationSchemes == null || !context.AuthenticationSchemes.Any()))
- {
- ChallengeContext = context;
- context.Accept(BaseOptions.AuthenticationScheme, BaseOptions.Description.Dictionary);
- }
-
- base.Challenge(context);
- }
-
- ///
- /// Automatic Authentication Handlers can handle empty authentication schemes
- ///
- ///
- public override bool ShouldHandleScheme(IEnumerable authenticationSchemes)
- {
- if (base.ShouldHandleScheme(authenticationSchemes))
- {
- return true;
- }
-
- return Options.AutomaticAuthentication &&
- (authenticationSchemes == null || !authenticationSchemes.Any());
- }
-
- ///
- /// Automatic Authentication Handlers can handle empty authentication schemes
- ///
- ///
- public override bool ShouldHandleScheme(string authenticationScheme)
- {
- if (base.ShouldHandleScheme(authenticationScheme))
- {
- return true;
- }
-
- return Options.AutomaticAuthentication && string.IsNullOrWhiteSpace(authenticationScheme);
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Authentication/AutomaticAuthenticationOptions.cs b/src/Microsoft.AspNet.Authentication/AutomaticAuthenticationOptions.cs
deleted file mode 100644
index 53af209846..0000000000
--- a/src/Microsoft.AspNet.Authentication/AutomaticAuthenticationOptions.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// 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 Microsoft.AspNet.Http.Authentication;
-
-namespace Microsoft.AspNet.Authentication
-{
- ///
- /// Base Options for all automatic authentication middleware
- ///
- public abstract class AutomaticAuthenticationOptions : AuthenticationOptions
- {
- ///
- /// If true the authentication middleware alter the request user coming in and
- /// alter 401 Unauthorized responses going out. If false the authentication middleware will only provide
- /// identity and alter responses when explicitly indicated by the AuthenticationScheme.
- ///
- public bool AutomaticAuthentication { get; set; }
- }
-}
diff --git a/src/Microsoft.AspNet.Authentication/ClaimsTransformationAppBuilderExtensions.cs b/src/Microsoft.AspNet.Authentication/ClaimsTransformationAppBuilderExtensions.cs
new file mode 100644
index 0000000000..79ecb38882
--- /dev/null
+++ b/src/Microsoft.AspNet.Authentication/ClaimsTransformationAppBuilderExtensions.cs
@@ -0,0 +1,26 @@
+// 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;
+using Microsoft.AspNet.Authentication;
+
+namespace Microsoft.AspNet.Builder
+{
+ ///
+ /// Extension methods provided by the claims transformation authentication middleware
+ ///
+ public static class ClaimsTransformationAppBuilderExtensions
+ {
+ ///
+ /// Adds a claims transformation middleware to your web application pipeline.
+ ///
+ /// The IApplicationBuilder passed to your configuration method
+ /// Used to configure the options for the middleware
+ /// The name of the options class that controls the middleware behavior, null will use the default options
+ /// The original app parameter
+ public static IApplicationBuilder UseClaimsTransformation(this IApplicationBuilder app)
+ {
+ return app.UseMiddleware();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Authentication/ClaimsTransformationAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication/ClaimsTransformationAuthenticationHandler.cs
new file mode 100644
index 0000000000..bee68e7d7c
--- /dev/null
+++ b/src/Microsoft.AspNet.Authentication/ClaimsTransformationAuthenticationHandler.cs
@@ -0,0 +1,105 @@
+// 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;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Http.Authentication;
+using Microsoft.AspNet.Http.Core.Authentication;
+
+namespace Microsoft.AspNet.Authentication
+{
+ ///
+ /// Handler that applies ClaimsTransformation to authentication
+ ///
+ public class ClaimsTransformationAuthenticationHandler : IAuthenticationHandler
+ {
+ private readonly Func _transform;
+
+ public ClaimsTransformationAuthenticationHandler(Func transform)
+ {
+ _transform = transform;
+ }
+
+ public IAuthenticationHandler PriorHandler { get; set; }
+
+ private void ApplyTransform(IAuthenticateContext context)
+ {
+ if (_transform != null)
+ {
+ // REVIEW: this cast seems really bad (missing interface way to get the result back out?)
+ var authContext = context as AuthenticateContext;
+ if (authContext?.Result?.Principal != null)
+ {
+ context.Authenticated(
+ _transform.Invoke(authContext.Result.Principal),
+ authContext.Result.Properties.Dictionary,
+ authContext.Result.Description.Dictionary);
+ }
+ }
+
+ }
+
+ public void Authenticate(IAuthenticateContext context)
+ {
+ if (PriorHandler != null)
+ {
+ PriorHandler.Authenticate(context);
+ ApplyTransform(context);
+ }
+ }
+
+ public async Task AuthenticateAsync(IAuthenticateContext context)
+ {
+ if (PriorHandler != null)
+ {
+ await PriorHandler.AuthenticateAsync(context);
+ ApplyTransform(context);
+ }
+ }
+
+ public void Challenge(IChallengeContext context)
+ {
+ if (PriorHandler != null)
+ {
+ PriorHandler.Challenge(context);
+ }
+ }
+
+ public void GetDescriptions(IDescribeSchemesContext context)
+ {
+ if (PriorHandler != null)
+ {
+ PriorHandler.GetDescriptions(context);
+ }
+ }
+
+ public void SignIn(ISignInContext context)
+ {
+ if (PriorHandler != null)
+ {
+ PriorHandler.SignIn(context);
+ }
+ }
+
+ public void SignOut(ISignOutContext context)
+ {
+ if (PriorHandler != null)
+ {
+ PriorHandler.SignOut(context);
+ }
+ }
+
+ public void RegisterAuthenticationHandler(IHttpAuthenticationFeature auth)
+ {
+ PriorHandler = auth.Handler;
+ auth.Handler = this;
+ }
+
+ public void UnregisterAuthenticationHandler(IHttpAuthenticationFeature auth)
+ {
+ auth.Handler = PriorHandler;
+ }
+
+ }
+}
diff --git a/src/Microsoft.AspNet.Authentication/ClaimsTransformationMiddleware.cs b/src/Microsoft.AspNet.Authentication/ClaimsTransformationMiddleware.cs
new file mode 100644
index 0000000000..cf3a58b792
--- /dev/null
+++ b/src/Microsoft.AspNet.Authentication/ClaimsTransformationMiddleware.cs
@@ -0,0 +1,44 @@
+// 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.Threading.Tasks;
+using Microsoft.AspNet.Builder;
+using Microsoft.AspNet.Http;
+using Microsoft.Framework.OptionsModel;
+using Microsoft.Framework.Internal;
+
+namespace Microsoft.AspNet.Authentication
+{
+ public class ClaimsTransformationMiddleware
+ {
+ private readonly RequestDelegate _next;
+
+ public ClaimsTransformationMiddleware(
+ [NotNull] RequestDelegate next,
+ [NotNull] IOptions options)
+ {
+ // REVIEW: do we need to take ConfigureOptions??
+ Options = options.Options;
+ _next = next;
+ }
+
+ public ClaimsTransformationOptions Options { get; set; }
+
+ public async Task Invoke(HttpContext context)
+ {
+ var handler = new ClaimsTransformationAuthenticationHandler(Options.Transformation);
+ handler.RegisterAuthenticationHandler(context.GetAuthentication());
+ try {
+ if (Options.Transformation != null)
+ {
+ context.User = Options.Transformation.Invoke(context.User);
+ }
+ await _next(context);
+ }
+ finally
+ {
+ handler.UnregisterAuthenticationHandler(context.GetAuthentication());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Authorization/ClaimsTransformationOptions.cs b/src/Microsoft.AspNet.Authentication/ClaimsTransformationOptions.cs
similarity index 64%
rename from src/Microsoft.AspNet.Authorization/ClaimsTransformationOptions.cs
rename to src/Microsoft.AspNet.Authentication/ClaimsTransformationOptions.cs
index da3a4a7bda..f8c4a3ed32 100644
--- a/src/Microsoft.AspNet.Authorization/ClaimsTransformationOptions.cs
+++ b/src/Microsoft.AspNet.Authentication/ClaimsTransformationOptions.cs
@@ -1,15 +1,13 @@
// 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;
using System.Security.Claims;
-using System.Threading.Tasks;
-namespace Microsoft.AspNet.Authorization
+namespace Microsoft.AspNet.Authentication
{
public class ClaimsTransformationOptions
{
- public Func> TransformAsync { get; set; }
+ public Func Transformation { get; set; }
}
}
diff --git a/src/Microsoft.AspNet.Authorization/ServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Authorization/ServiceCollectionExtensions.cs
index e825684b3d..467b36aff4 100644
--- a/src/Microsoft.AspNet.Authorization/ServiceCollectionExtensions.cs
+++ b/src/Microsoft.AspNet.Authorization/ServiceCollectionExtensions.cs
@@ -9,11 +9,6 @@ namespace Microsoft.Framework.DependencyInjection
{
public static class ServiceCollectionExtensions
{
- public static IServiceCollection ConfigureClaimsTransformation([NotNull] this IServiceCollection services, [NotNull] Action configure)
- {
- return services.Configure(configure);
- }
-
public static IServiceCollection ConfigureAuthorization([NotNull] this IServiceCollection services, [NotNull] Action configure)
{
return services.Configure(configure);
diff --git a/test/Microsoft.AspNet.Authentication.Test/AuthenticationHandlerFacts.cs b/test/Microsoft.AspNet.Authentication.Test/AuthenticationHandlerFacts.cs
index 6bd62b2b8b..8ed1063dd7 100644
--- a/test/Microsoft.AspNet.Authentication.Test/AuthenticationHandlerFacts.cs
+++ b/test/Microsoft.AspNet.Authentication.Test/AuthenticationHandlerFacts.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using Microsoft.AspNet.Authentication;
using Microsoft.AspNet.Http.Core;
using Xunit;
@@ -79,9 +78,15 @@ namespace Microsoft.AspNet.Authentication
private class TestOptions : AuthenticationOptions { }
- private class TestAutoOptions : AutomaticAuthenticationOptions { }
+ private class TestAutoOptions : AuthenticationOptions
+ {
+ public TestAutoOptions()
+ {
+ AutomaticAuthentication = true;
+ }
+ }
- private class TestAutoHandler : AutomaticAuthenticationHandler
+ private class TestAutoHandler : AuthenticationHandler
{
public TestAutoHandler(string scheme, bool auto)
{
diff --git a/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs b/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs
index 51d9a9452c..ea38856504 100644
--- a/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs
+++ b/test/Microsoft.AspNet.Authentication.Test/Cookies/CookieMiddlewareTests.cs
@@ -34,37 +34,47 @@ namespace Microsoft.AspNet.Authentication.Cookies
response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
- [Fact]
- public async Task ProtectedRequestShouldRedirectToLogin()
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task ProtectedRequestShouldRedirectToLoginOnlyWhenAutomatic(bool auto)
{
TestServer server = CreateServer(options =>
{
options.LoginPath = new PathString("/login");
+ options.AutomaticAuthentication = auto;
});
Transaction transaction = await SendAsync(server, "http://example.com/protected");
- transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
-
- Uri location = transaction.Response.Headers.Location;
- location.LocalPath.ShouldBe("/login");
- location.Query.ShouldBe("?ReturnUrl=%2Fprotected");
+ transaction.Response.StatusCode.ShouldBe(auto ? HttpStatusCode.Redirect : HttpStatusCode.Unauthorized);
+ if (auto)
+ {
+ Uri location = transaction.Response.Headers.Location;
+ location.LocalPath.ShouldBe("/login");
+ location.Query.ShouldBe("?ReturnUrl=%2Fprotected");
+ }
}
- [Fact]
- public async Task ProtectedCustomRequestShouldRedirectToCustomLogin()
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task ProtectedCustomRequestShouldRedirectToCustomLogin(bool auto)
{
TestServer server = CreateServer(options =>
{
options.LoginPath = new PathString("/login");
+ options.AutomaticAuthentication = auto;
});
Transaction transaction = await SendAsync(server, "http://example.com/protected/CustomRedirect");
- transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
-
- Uri location = transaction.Response.Headers.Location;
- location.ToString().ShouldBe("/CustomRedirect");
+ transaction.Response.StatusCode.ShouldBe(auto ? HttpStatusCode.Redirect : HttpStatusCode.Unauthorized);
+ if (auto)
+ {
+ Uri location = transaction.Response.Headers.Location;
+ location.ToString().ShouldBe("/CustomRedirect");
+ }
}
private Task SignInAsAlice(HttpContext context)
@@ -221,6 +231,37 @@ namespace Microsoft.AspNet.Authentication.Cookies
FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe("Alice");
}
+ [Fact]
+ public async Task CookieAppliesClaimsTransform()
+ {
+ var clock = new TestClock();
+ TestServer server = CreateServer(options =>
+ {
+ options.SystemClock = clock;
+ },
+ SignInAsAlice,
+ baseAddress: null,
+ claimsTransform: o => o.Transformation = (p =>
+ {
+ if (!p.Identities.Any(i => i.AuthenticationType == "xform"))
+ {
+ // REVIEW: Xform runs twice, once on Authenticate, and then once from the middleware
+ var id = new ClaimsIdentity("xform");
+ id.AddClaim(new Claim("xform", "yup"));
+ p.AddIdentity(id);
+ }
+ return p;
+ }));
+
+ Transaction transaction1 = await SendAsync(server, "http://example.com/testpath");
+
+ Transaction transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
+
+ FindClaimValue(transaction2, ClaimTypes.Name).ShouldBe("Alice");
+ FindClaimValue(transaction2, "xform").ShouldBe("yup");
+
+ }
+
[Fact]
public async Task CookieStopsWorkingAfterExpiration()
{
@@ -372,6 +413,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
TestServer server = CreateServer(options =>
{
options.LoginPath = new PathString("/login");
+ options.AutomaticAuthentication = true;
});
Transaction transaction = await SendAsync(server, "http://example.com/protected", ajaxRequest: true);
@@ -478,12 +520,26 @@ namespace Microsoft.AspNet.Authentication.Cookies
return me;
}
- private static TestServer CreateServer(Action configureOptions, Func testpath = null, Uri baseAddress = null)
+ private static TestServer CreateServer(Action configureOptions, Func testpath = null, Uri baseAddress = null, Action claimsTransform = null)
{
var server = TestServer.Create(app =>
{
- app.UseServices(services => services.AddDataProtection());
+ if (claimsTransform != null)
+ {
+ app.UseServices(services => {
+ services.AddDataProtection();
+ services.ConfigureClaimsTransformation(claimsTransform);
+ });
+ }
+ else
+ {
+ app.UseServices(services => services.AddDataProtection());
+ }
app.UseCookieAuthentication(configureOptions);
+ if (claimsTransform != null)
+ {
+ app.UseClaimsTransformation();
+ }
app.Use(async (context, next) =>
{
var req = context.Request;
diff --git a/test/Microsoft.AspNet.Authentication.Test/Facebook/FacebookMiddlewareTests.cs b/test/Microsoft.AspNet.Authentication.Test/Facebook/FacebookMiddlewareTests.cs
index a0a5558e81..ba00fb1253 100644
--- a/test/Microsoft.AspNet.Authentication.Test/Facebook/FacebookMiddlewareTests.cs
+++ b/test/Microsoft.AspNet.Authentication.Test/Facebook/FacebookMiddlewareTests.cs
@@ -42,6 +42,7 @@ namespace Microsoft.AspNet.Authentication.Facebook
services.ConfigureCookieAuthentication(options =>
{
options.AuthenticationScheme = "External";
+ options.AutomaticAuthentication = true;
});
services.Configure(options =>
{
diff --git a/test/Microsoft.AspNet.Authentication.Test/Google/GoogleMiddlewareTests.cs b/test/Microsoft.AspNet.Authentication.Test/Google/GoogleMiddlewareTests.cs
index 4e02276be6..45ba05777d 100644
--- a/test/Microsoft.AspNet.Authentication.Test/Google/GoogleMiddlewareTests.cs
+++ b/test/Microsoft.AspNet.Authentication.Test/Google/GoogleMiddlewareTests.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
@@ -18,6 +19,7 @@ using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.TestHost;
using Microsoft.Framework.DependencyInjection;
+using Microsoft.Framework.WebEncoders;
using Newtonsoft.Json;
using Shouldly;
using Xunit;
@@ -50,6 +52,25 @@ namespace Microsoft.AspNet.Authentication.Google
location.ShouldNotContain("login_hint=");
}
+ [Fact]
+ public async Task Challenge401WillTriggerRedirection()
+ {
+ var server = CreateServer(options =>
+ {
+ options.ClientId = "Test Id";
+ options.ClientSecret = "Test Secret";
+ options.AutomaticAuthentication = true;
+ });
+ var transaction = await SendAsync(server, "https://example.com/401");
+ transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
+ var location = transaction.Response.Headers.Location.ToString();
+ location.ShouldContain("https://accounts.google.com/o/oauth2/auth?response_type=code");
+ location.ShouldContain("&client_id=");
+ location.ShouldContain("&redirect_uri=");
+ location.ShouldContain("&scope=");
+ location.ShouldContain("&state=");
+ }
+
[Fact]
public async Task ChallengeWillSetCorrelationCookie()
{
@@ -63,6 +84,20 @@ namespace Microsoft.AspNet.Authentication.Google
transaction.SetCookie.Single().ShouldContain(".AspNet.Correlation.Google=");
}
+ [Fact]
+ public async Task Challenge401WillSetCorrelationCookie()
+ {
+ var server = CreateServer(options =>
+ {
+ options.ClientId = "Test Id";
+ options.ClientSecret = "Test Secret";
+ options.AutomaticAuthentication = true;
+ });
+ var transaction = await SendAsync(server, "https://example.com/401");
+ Console.WriteLine(transaction.SetCookie);
+ transaction.SetCookie.Single().ShouldContain(".AspNet.Correlation.Google=");
+ }
+
[Fact]
public async Task ChallengeWillSetDefaultScope()
{
@@ -78,18 +113,18 @@ namespace Microsoft.AspNet.Authentication.Google
}
[Fact(Skip = "Failing due to : https://github.com/aspnet/HttpAbstractions/issues/231")]
- public async Task ChallengeWillUseOptionsScope()
+ public async Task Challenge401WillSetDefaultScope()
{
var server = CreateServer(options =>
{
options.ClientId = "Test Id";
options.ClientSecret = "Test Secret";
- options.Scope.Add("https://www.googleapis.com/auth/plus.login");
+ options.AutomaticAuthentication = true;
});
- var transaction = await SendAsync(server, "https://example.com/challenge");
+ var transaction = await SendAsync(server, "https://example.com/401");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
var query = transaction.Response.Headers.Location.Query;
- query.ShouldContain("&scope=" + Uri.EscapeDataString("https://www.googleapis.com/auth/plus.login"));
+ query.ShouldContain("&scope=" + Uri.EscapeDataString("openid profile email"));
}
[Fact(Skip = "Failing due to : https://github.com/aspnet/HttpAbstractions/issues/231")]
@@ -99,6 +134,7 @@ namespace Microsoft.AspNet.Authentication.Google
{
options.ClientId = "Test Id";
options.ClientSecret = "Test Secret";
+ options.AutomaticAuthentication = true;
},
context =>
{
@@ -122,10 +158,10 @@ namespace Microsoft.AspNet.Authentication.Google
var transaction = await SendAsync(server, "https://example.com/challenge2");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
var query = transaction.Response.Headers.Location.Query;
- query.ShouldContain("scope=" + Uri.EscapeDataString("https://www.googleapis.com/auth/plus.login"));
+ query.ShouldContain("scope=" + UrlEncoder.Default.UrlEncode("https://www.googleapis.com/auth/plus.login"));
query.ShouldContain("access_type=offline");
query.ShouldContain("approval_prompt=force");
- query.ShouldContain("login_hint=" + Uri.EscapeDataString("test@example.com"));
+ query.ShouldContain("login_hint=" + UrlEncoder.Default.UrlEncode("test@example.com"));
}
[Fact]
@@ -149,6 +185,35 @@ namespace Microsoft.AspNet.Authentication.Google
query.ShouldContain("custom=test");
}
+ // TODO: Fix these tests to path (Need some test logic for Authenticate("Google") to return a ticket still
+ //[Fact]
+ //public async Task GoogleTurns401To403WhenAuthenticated()
+ //{
+ // TestServer server = CreateServer(options =>
+ // {
+ // options.ClientId = "Test Id";
+ // options.ClientSecret = "Test Secret";
+ // });
+
+ // Transaction transaction1 = await SendAsync(server, "http://example.com/unauthorized");
+ // transaction1.Response.StatusCode.ShouldBe(HttpStatusCode.Forbidden);
+ //}
+
+ //[Fact]
+ //public async Task GoogleTurns401To403WhenAutomatic()
+ //{
+ // TestServer server = CreateServer(options =>
+ // {
+ // options.ClientId = "Test Id";
+ // options.ClientSecret = "Test Secret";
+ // options.AutomaticAuthentication = true;
+ // });
+
+ // Debugger.Launch();
+ // Transaction transaction1 = await SendAsync(server, "http://example.com/unauthorizedAuto");
+ // transaction1.Response.StatusCode.ShouldBe(HttpStatusCode.Forbidden);
+ //}
+
[Fact]
public async Task ReplyPathWithoutStateQueryStringWillBeRejected()
{
@@ -233,6 +298,9 @@ namespace Microsoft.AspNet.Authentication.Google
transaction.FindClaimValue(ClaimTypes.GivenName).ShouldBe("Test Given Name");
transaction.FindClaimValue(ClaimTypes.Surname).ShouldBe("Test Family Name");
transaction.FindClaimValue(ClaimTypes.Email).ShouldBe("Test email");
+
+ // Ensure claims transformation
+ transaction.FindClaimValue("xform").ShouldBe("yup");
}
[Fact]
@@ -421,9 +489,21 @@ namespace Microsoft.AspNet.Authentication.Google
{
options.SignInScheme = CookieAuthenticationScheme;
});
+ services.ConfigureClaimsTransformation(p =>
+ {
+ var id = new ClaimsIdentity("xform");
+ id.AddClaim(new Claim("xform", "yup"));
+ p.AddIdentity(id);
+ return p;
+ });
+ });
+ app.UseCookieAuthentication(options =>
+ {
+ options.AuthenticationScheme = CookieAuthenticationScheme;
+ options.AutomaticAuthentication = true;
});
- app.UseCookieAuthentication(options => options.AuthenticationScheme = CookieAuthenticationScheme);
app.UseGoogleAuthentication(configureOptions);
+ app.UseClaimsTransformation();
app.Use(async (context, next) =>
{
var req = context.Request;
@@ -437,6 +517,18 @@ namespace Microsoft.AspNet.Authentication.Google
{
Describe(res, context.User);
}
+ else if (req.Path == new PathString("/unauthorized"))
+ {
+ // Simulate Authorization failure
+ var result = await context.AuthenticateAsync("Google");
+ res.Challenge("Google");
+ }
+ else if (req.Path == new PathString("/unauthorizedAuto"))
+ {
+ var result = await context.AuthenticateAsync("Google");
+ res.StatusCode = 401;
+ res.Challenge();
+ }
else if (req.Path == new PathString("/401"))
{
res.StatusCode = 401;
diff --git a/test/Microsoft.AspNet.Authentication.Test/MicrosoftAccount/MicrosoftAccountMiddlewareTests.cs b/test/Microsoft.AspNet.Authentication.Test/MicrosoftAccount/MicrosoftAccountMiddlewareTests.cs
index e7a8fccd3b..48372859df 100644
--- a/test/Microsoft.AspNet.Authentication.Test/MicrosoftAccount/MicrosoftAccountMiddlewareTests.cs
+++ b/test/Microsoft.AspNet.Authentication.Test/MicrosoftAccount/MicrosoftAccountMiddlewareTests.cs
@@ -169,7 +169,11 @@ namespace Microsoft.AspNet.Authentication.Tests.MicrosoftAccount
options.SignInScheme = "External";
});
});
- app.UseCookieAuthentication(options => options.AuthenticationScheme = "External");
+ app.UseCookieAuthentication(options =>
+ {
+ options.AuthenticationScheme = "External";
+ options.AutomaticAuthentication = true;
+ });
app.UseMicrosoftAccountAuthentication(configureOptions);
app.Use(async (context, next) =>
{