diff --git a/HttpAbstractions.sln b/HttpAbstractions.sln
index 9525343a98..6434e357d0 100644
--- a/HttpAbstractions.sln
+++ b/HttpAbstractions.sln
@@ -57,6 +57,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{ED7BCAC5
build\Key.snk = build\Key.snk
EndProjectSection
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.Abstractions", "src\Microsoft.AspNetCore.Authentication.Abstractions\Microsoft.AspNetCore.Authentication.Abstractions.csproj", "{3D8C9A87-5DFB-4EC0-9CB6-174AD3B33852}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.Core", "src\Microsoft.AspNetCore.Authentication.Core\Microsoft.AspNetCore.Authentication.Core.csproj", "{73CA3145-91BD-4DA5-BC74-40008DE7EA98}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authentication.Core.Test", "test\Microsoft.AspNetCore.Authentication.Core.Test\Microsoft.AspNetCore.Authentication.Core.Test.csproj", "{A85950C5-2794-47E2-8EAA-05A1DC7C6DA7}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -223,6 +229,42 @@ Global
{1D0764B4-1DEB-4232-A714-D4B7E846918A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{1D0764B4-1DEB-4232-A714-D4B7E846918A}.Release|x86.ActiveCfg = Release|Any CPU
{1D0764B4-1DEB-4232-A714-D4B7E846918A}.Release|x86.Build.0 = Release|Any CPU
+ {3D8C9A87-5DFB-4EC0-9CB6-174AD3B33852}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3D8C9A87-5DFB-4EC0-9CB6-174AD3B33852}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3D8C9A87-5DFB-4EC0-9CB6-174AD3B33852}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {3D8C9A87-5DFB-4EC0-9CB6-174AD3B33852}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {3D8C9A87-5DFB-4EC0-9CB6-174AD3B33852}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3D8C9A87-5DFB-4EC0-9CB6-174AD3B33852}.Debug|x86.Build.0 = Debug|Any CPU
+ {3D8C9A87-5DFB-4EC0-9CB6-174AD3B33852}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3D8C9A87-5DFB-4EC0-9CB6-174AD3B33852}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3D8C9A87-5DFB-4EC0-9CB6-174AD3B33852}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {3D8C9A87-5DFB-4EC0-9CB6-174AD3B33852}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {3D8C9A87-5DFB-4EC0-9CB6-174AD3B33852}.Release|x86.ActiveCfg = Release|Any CPU
+ {3D8C9A87-5DFB-4EC0-9CB6-174AD3B33852}.Release|x86.Build.0 = Release|Any CPU
+ {73CA3145-91BD-4DA5-BC74-40008DE7EA98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {73CA3145-91BD-4DA5-BC74-40008DE7EA98}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {73CA3145-91BD-4DA5-BC74-40008DE7EA98}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {73CA3145-91BD-4DA5-BC74-40008DE7EA98}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {73CA3145-91BD-4DA5-BC74-40008DE7EA98}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {73CA3145-91BD-4DA5-BC74-40008DE7EA98}.Debug|x86.Build.0 = Debug|Any CPU
+ {73CA3145-91BD-4DA5-BC74-40008DE7EA98}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {73CA3145-91BD-4DA5-BC74-40008DE7EA98}.Release|Any CPU.Build.0 = Release|Any CPU
+ {73CA3145-91BD-4DA5-BC74-40008DE7EA98}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {73CA3145-91BD-4DA5-BC74-40008DE7EA98}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {73CA3145-91BD-4DA5-BC74-40008DE7EA98}.Release|x86.ActiveCfg = Release|Any CPU
+ {73CA3145-91BD-4DA5-BC74-40008DE7EA98}.Release|x86.Build.0 = Release|Any CPU
+ {A85950C5-2794-47E2-8EAA-05A1DC7C6DA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A85950C5-2794-47E2-8EAA-05A1DC7C6DA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A85950C5-2794-47E2-8EAA-05A1DC7C6DA7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {A85950C5-2794-47E2-8EAA-05A1DC7C6DA7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {A85950C5-2794-47E2-8EAA-05A1DC7C6DA7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A85950C5-2794-47E2-8EAA-05A1DC7C6DA7}.Debug|x86.Build.0 = Debug|Any CPU
+ {A85950C5-2794-47E2-8EAA-05A1DC7C6DA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A85950C5-2794-47E2-8EAA-05A1DC7C6DA7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A85950C5-2794-47E2-8EAA-05A1DC7C6DA7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {A85950C5-2794-47E2-8EAA-05A1DC7C6DA7}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {A85950C5-2794-47E2-8EAA-05A1DC7C6DA7}.Release|x86.ActiveCfg = Release|Any CPU
+ {A85950C5-2794-47E2-8EAA-05A1DC7C6DA7}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -244,5 +286,8 @@ Global
{E6BB7AD1-BD10-4A23-B780-F4A86ADF00D1} = {F31FF137-390C-49BF-A3BD-7C6ED3597C21}
{1D0764B4-1DEB-4232-A714-D4B7E846918A} = {982F09D8-621E-4872-BA7B-BBDEA47D1EFD}
{ED7BCAC5-2796-44BD-9954-7C248263BC8B} = {C6C48D5F-B289-4150-A6FC-77A5C7064BCE}
+ {3D8C9A87-5DFB-4EC0-9CB6-174AD3B33852} = {A5A15F1C-885A-452A-A731-B0173DDBD913}
+ {73CA3145-91BD-4DA5-BC74-40008DE7EA98} = {A5A15F1C-885A-452A-A731-B0173DDBD913}
+ {A85950C5-2794-47E2-8EAA-05A1DC7C6DA7} = {F31FF137-390C-49BF-A3BD-7C6ED3597C21}
EndGlobalSection
EndGlobal
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticateContext.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticateContext.cs
new file mode 100644
index 0000000000..814d7024e4
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticateContext.cs
@@ -0,0 +1,21 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Base class used by methods.
+ ///
+ public class AuthenticateContext : BaseAuthenticationContext
+ {
+ ///
+ /// Constructor.
+ ///
+ /// The context.
+ /// The name of the authentication scheme.
+ public AuthenticateContext(HttpContext context, string authenticationScheme) : base(context, authenticationScheme, properties: null)
+ { }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticateResult.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticateResult.cs
new file mode 100644
index 0000000000..bb9bbb9716
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticateResult.cs
@@ -0,0 +1,105 @@
+// Copyright (c) .NET Foundation. 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;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Contains the result of an Authenticate call
+ ///
+ public class AuthenticateResult
+ {
+ private AuthenticateResult() { }
+
+ ///
+ /// If a ticket was produced, authenticate was successful.
+ ///
+ public bool Succeeded => Ticket != null;
+
+ ///
+ /// The authentication ticket.
+ ///
+ public AuthenticationTicket Ticket { get; private set; }
+
+ ///
+ /// Gets the claims-principal with authenticated user identities.
+ ///
+ public ClaimsPrincipal Principal => Ticket?.Principal;
+
+ ///
+ /// Additional state values for the authentication session.
+ ///
+ public AuthenticationProperties Properties => Ticket?.Properties;
+
+ ///
+ /// Holds failure information from the authentication.
+ ///
+ public Exception Failure { get; private set; }
+
+ ///
+ /// Indicates that stage of authentication was directly handled by user intervention and no
+ /// further processing should be attempted.
+ ///
+ public bool Handled { get; private set; }
+
+ ///
+ /// Indicates that there was no information returned for this authentication scheme.
+ ///
+ public bool Nothing { get; private set; }
+
+ ///
+ /// Indicates that authentication was successful.
+ ///
+ /// The ticket representing the authentication result.
+ /// The result.
+ public static AuthenticateResult Success(AuthenticationTicket ticket)
+ {
+ if (ticket == null)
+ {
+ throw new ArgumentNullException(nameof(ticket));
+ }
+ return new AuthenticateResult() { Ticket = ticket };
+ }
+
+ ///
+ /// Indicates that stage of authentication was directly handled by user intervention and no
+ /// further processing should be attempted.
+ ///
+ /// The result.
+ public static AuthenticateResult Handle()
+ {
+ return new AuthenticateResult() { Handled = true };
+ }
+
+ ///
+ /// Indicates that there was no information returned for this authentication scheme.
+ ///
+ /// The result.
+ public static AuthenticateResult None()
+ {
+ return new AuthenticateResult() { Nothing = true };
+ }
+
+ ///
+ /// Indicates that there was a failure during authentication.
+ ///
+ /// The failure exception.
+ /// The result.
+ public static AuthenticateResult Fail(Exception failure)
+ {
+ return new AuthenticateResult() { Failure = failure };
+ }
+
+ ///
+ /// Indicates that there was a failure during authentication.
+ ///
+ /// The failure message.
+ /// The result.
+ public static AuthenticateResult Fail(string failureMessage)
+ {
+ return new AuthenticateResult() { Failure = new Exception(failureMessage) };
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationHttpContextExtensions.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationHttpContextExtensions.cs
new file mode 100644
index 0000000000..a1fd3756ef
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationHttpContextExtensions.cs
@@ -0,0 +1,158 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Extension methods to expose Authentication on HttpContext.
+ ///
+ public static class AuthenticationHttpContextExtensions
+ {
+ ///
+ /// Extension method for authenticate using the scheme.
+ ///
+ /// The context.
+ /// The .
+ public static Task AuthenticateAsync(this HttpContext context) =>
+ context.AuthenticateAsync(scheme: null);
+
+ ///
+ /// Extension method for authenticate.
+ ///
+ /// The context.
+ /// The name of the authentication scheme.
+ /// The .
+ public static Task AuthenticateAsync(this HttpContext context, string scheme) =>
+ context.RequestServices.GetRequiredService().AuthenticateAsync(context, scheme);
+
+ ///
+ /// Extension method for Challenge.
+ ///
+ /// The context.
+ /// The name of the authentication scheme.
+ /// The result.
+ public static Task ChallengeAsync(this HttpContext context, string scheme) =>
+ context.ChallengeAsync(scheme, properties: null);
+
+ ///
+ /// Extension method for authenticate using the scheme.
+ ///
+ /// The context.
+ /// The task.
+ public static Task ChallengeAsync(this HttpContext context) =>
+ context.ChallengeAsync(scheme: null, properties: null);
+
+ ///
+ /// Extension method for Challenge.
+ ///
+ /// The context.
+ /// The name of the authentication scheme.
+ /// The properties.
+ /// The task.
+ public static Task ChallengeAsync(this HttpContext context, string scheme, AuthenticationProperties properties) =>
+ context.ChallengeAsync(scheme, properties: properties, behavior: ChallengeBehavior.Automatic);
+
+ ///
+ /// Extension method for Challenge.
+ ///
+ /// The context.
+ /// The name of the authentication scheme.
+ /// The properties.
+ /// The behavior.
+ /// The task.
+ public static Task ChallengeAsync(this HttpContext context, string scheme, AuthenticationProperties properties, ChallengeBehavior behavior) =>
+ context.RequestServices.GetRequiredService().ChallengeAsync(context, scheme, properties, behavior);
+
+ ///
+ /// Extension method for Forbid.
+ ///
+ /// The context.
+ /// The name of the authentication scheme.
+ /// The task.
+ public static Task ForbidAsync(this HttpContext context, string scheme) =>
+ context.ForbidAsync(scheme, properties: null);
+
+ ///
+ /// Extension method for Forbid.
+ ///
+ /// The context.
+ /// The name of the authentication scheme.
+ /// The properties.
+ /// The task.
+ public static Task ForbidAsync(this HttpContext context, string scheme, AuthenticationProperties properties) =>
+ context.RequestServices.GetRequiredService().ChallengeAsync(context, scheme, properties, ChallengeBehavior.Forbidden);
+
+ ///
+ /// Extension method for SignIn.
+ ///
+ /// The context.
+ /// The name of the authentication scheme.
+ /// The user.
+ /// The task.
+ public static Task SignInAsync(this HttpContext context, string scheme, ClaimsPrincipal principal) =>
+ context.SignInAsync(scheme, principal, properties: null);
+
+ ///
+ /// Extension method for SignIn using the .
+ ///
+ /// The context.
+ /// The user.
+ /// The task.
+ public static Task SignInAsync(this HttpContext context, ClaimsPrincipal principal) =>
+ context.SignInAsync(scheme: null, principal: principal, properties: null);
+
+ ///
+ /// Extension method for SignIn using the .
+ ///
+ /// The context.
+ /// The user.
+ /// The properties.
+ /// The task.
+ public static Task SignInAsync(this HttpContext context, ClaimsPrincipal principal, AuthenticationProperties properties) =>
+ context.SignInAsync(scheme: null, principal: principal, properties: properties);
+
+ ///
+ /// Extension method for SignIn.
+ ///
+ /// The context.
+ /// The name of the authentication scheme.
+ /// The user.
+ /// The properties.
+ /// The task.
+ public static Task SignInAsync(this HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties) =>
+ context.RequestServices.GetRequiredService().SignInAsync(context, scheme, principal, properties);
+
+ ///
+ /// Extension method for SignOut.
+ ///
+ /// The context.
+ /// The name of the authentication scheme.
+ /// The task.
+ public static Task SignOutAsync(this HttpContext context, string scheme) => context.SignOutAsync(scheme, properties: null);
+
+ ///
+ /// Extension method for SignOut.
+ ///
+ /// The context.
+ /// The name of the authentication scheme.
+ /// The properties.
+ ///
+ public static Task SignOutAsync(this HttpContext context, string scheme, AuthenticationProperties properties) =>
+ context.RequestServices.GetRequiredService().SignOutAsync(context, scheme, properties);
+
+ ///
+ /// Extension method for getting the value of an authentication token.
+ ///
+ /// The context.
+ /// The name of the authentication scheme.
+ /// The name of the token.
+ /// The value of the token.
+ public static Task GetTokenAsync(this HttpContext context, string scheme, string tokenName) =>
+ context.RequestServices.GetRequiredService().GetTokenAsync(context, scheme, tokenName);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationOptions.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationOptions.cs
new file mode 100644
index 0000000000..e7e5936e17
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationOptions.cs
@@ -0,0 +1,65 @@
+// Copyright (c) .NET Foundation. 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.Collections.Generic;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ public class AuthenticationOptions
+ {
+ private readonly IList _schemes = new List();
+
+ ///
+ /// Returns the schemes in the order they were added (important for request handling priority)
+ ///
+ public IEnumerable Schemes => _schemes;
+
+ ///
+ /// Maps schemes by name.
+ ///
+ public IDictionary SchemeMap { get; } = new Dictionary(StringComparer.Ordinal);
+
+ ///
+ /// Adds an .
+ ///
+ /// The name of the scheme being added.
+ /// Configures the scheme.
+ public void AddScheme(string name, Action configureBuilder)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+ if (configureBuilder == null)
+ {
+ throw new ArgumentNullException(nameof(configureBuilder));
+ }
+ if (SchemeMap.ContainsKey(name))
+ {
+ throw new InvalidOperationException("Scheme already exists: " + name);
+ }
+
+ var builder = new AuthenticationSchemeBuilder(name);
+ configureBuilder(builder);
+ _schemes.Add(builder);
+ SchemeMap[name] = builder;
+ }
+
+ ///
+ /// Used by as the default scheme by .
+ ///
+ public string DefaultAuthenticationScheme { get; set; }
+
+ ///
+ /// Used by as the default scheme by .
+ ///
+ public string DefaultSignInScheme { get; set; }
+
+ ///
+ /// Used by as the default scheme by .
+ ///
+ public string DefaultChallengeScheme { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationProperties.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationProperties.cs
new file mode 100644
index 0000000000..609b6fad58
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationProperties.cs
@@ -0,0 +1,197 @@
+// Copyright (c) .NET Foundation. 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.Collections.Generic;
+using System.Globalization;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Dictionary used to store state values about the authentication session.
+ ///
+ public class AuthenticationProperties
+ {
+ internal const string IssuedUtcKey = ".issued";
+ internal const string ExpiresUtcKey = ".expires";
+ internal const string IsPersistentKey = ".persistent";
+ internal const string RedirectUriKey = ".redirect";
+ internal const string RefreshKey = ".refresh";
+ internal const string UtcDateTimeFormat = "r";
+
+ ///
+ /// Initializes a new instance of the class
+ ///
+ public AuthenticationProperties()
+ : this(items: null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ ///
+ ///
+ public AuthenticationProperties(IDictionary items)
+ {
+ Items = items ?? new Dictionary(StringComparer.Ordinal);
+ }
+
+ ///
+ /// State values about the authentication session.
+ ///
+ public IDictionary Items { get; }
+
+ ///
+ /// Gets or sets whether the authentication session is persisted across multiple requests.
+ ///
+ public bool IsPersistent
+ {
+ get { return Items.ContainsKey(IsPersistentKey); }
+ set
+ {
+ if (Items.ContainsKey(IsPersistentKey))
+ {
+ if (!value)
+ {
+ Items.Remove(IsPersistentKey);
+ }
+ }
+ else
+ {
+ if (value)
+ {
+ Items.Add(IsPersistentKey, string.Empty);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the full path or absolute URI to be used as an http redirect response value.
+ ///
+ public string RedirectUri
+ {
+ get
+ {
+ string value;
+ return Items.TryGetValue(RedirectUriKey, out value) ? value : null;
+ }
+ set
+ {
+ if (value != null)
+ {
+ Items[RedirectUriKey] = value;
+ }
+ else
+ {
+ if (Items.ContainsKey(RedirectUriKey))
+ {
+ Items.Remove(RedirectUriKey);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the time at which the authentication ticket was issued.
+ ///
+ public DateTimeOffset? IssuedUtc
+ {
+ get
+ {
+ string value;
+ if (Items.TryGetValue(IssuedUtcKey, out value))
+ {
+ DateTimeOffset dateTimeOffset;
+ if (DateTimeOffset.TryParseExact(value, UtcDateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTimeOffset))
+ {
+ return dateTimeOffset;
+ }
+ }
+ return null;
+ }
+ set
+ {
+ if (value.HasValue)
+ {
+ Items[IssuedUtcKey] = value.Value.ToString(UtcDateTimeFormat, CultureInfo.InvariantCulture);
+ }
+ else
+ {
+ if (Items.ContainsKey(IssuedUtcKey))
+ {
+ Items.Remove(IssuedUtcKey);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the time at which the authentication ticket expires.
+ ///
+ public DateTimeOffset? ExpiresUtc
+ {
+ get
+ {
+ string value;
+ if (Items.TryGetValue(ExpiresUtcKey, out value))
+ {
+ DateTimeOffset dateTimeOffset;
+ if (DateTimeOffset.TryParseExact(value, UtcDateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTimeOffset))
+ {
+ return dateTimeOffset;
+ }
+ }
+ return null;
+ }
+ set
+ {
+ if (value.HasValue)
+ {
+ Items[ExpiresUtcKey] = value.Value.ToString(UtcDateTimeFormat, CultureInfo.InvariantCulture);
+ }
+ else
+ {
+ if (Items.ContainsKey(ExpiresUtcKey))
+ {
+ Items.Remove(ExpiresUtcKey);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets if refreshing the authentication session should be allowed.
+ ///
+ public bool? AllowRefresh
+ {
+ get
+ {
+ string value;
+ if (Items.TryGetValue(RefreshKey, out value))
+ {
+ bool refresh;
+ if (bool.TryParse(value, out refresh))
+ {
+ return refresh;
+ }
+ }
+ return null;
+ }
+ set
+ {
+ if (value.HasValue)
+ {
+ Items[RefreshKey] = value.Value.ToString();
+ }
+ else
+ {
+ if (Items.ContainsKey(RefreshKey))
+ {
+ Items.Remove(RefreshKey);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationScheme.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationScheme.cs
new file mode 100644
index 0000000000..77bd9e6d35
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationScheme.cs
@@ -0,0 +1,49 @@
+// Copyright (c) .NET Foundation. 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.Reflection;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// AuthenticationSchemes assign a name to a specific
+ /// handlerType.
+ ///
+ public class AuthenticationScheme
+ {
+ ///
+ /// Constructor.
+ ///
+ /// The name for the authentication scheme.
+ /// The type that handles this scheme.
+ public AuthenticationScheme(string name, Type handlerType)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+ if (handlerType == null)
+ {
+ throw new ArgumentNullException(nameof(handlerType));
+ }
+ if (!typeof(IAuthenticationHandler).IsAssignableFrom(handlerType))
+ {
+ throw new ArgumentException("handlerType must implement IAuthenticationSchemeHandler.");
+ }
+
+ Name = name;
+ HandlerType = handlerType;
+ }
+
+ ///
+ /// The name of the authentication scheme.
+ ///
+ public string Name { get; }
+
+ ///
+ /// The type that handles this scheme.
+ ///
+ public Type HandlerType { get; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationSchemeBuilder.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationSchemeBuilder.cs
new file mode 100644
index 0000000000..e1ea0cbc4a
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationSchemeBuilder.cs
@@ -0,0 +1,38 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Used to build s.
+ ///
+ public class AuthenticationSchemeBuilder
+ {
+ ///
+ /// Constructor.
+ ///
+ /// The name of the scheme being built.
+ public AuthenticationSchemeBuilder(string name)
+ {
+ Name = name;
+ }
+
+ ///
+ /// The name of the scheme being built.
+ ///
+ public string Name { get; }
+
+ ///
+ /// The type responsible for this scheme.
+ ///
+ public Type HandlerType { get; set; }
+
+ ///
+ /// Builds the instance.
+ ///
+ ///
+ public AuthenticationScheme Build() => new AuthenticationScheme(Name, HandlerType);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationTicket.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationTicket.cs
new file mode 100644
index 0000000000..c31f15ec01
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationTicket.cs
@@ -0,0 +1,56 @@
+// Copyright (c) .NET Foundation. 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;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Contains user identity information as well as additional authentication state.
+ ///
+ public class AuthenticationTicket
+ {
+ ///
+ /// Initializes a new instance of the class
+ ///
+ /// the that represents the authenticated user.
+ /// additional properties that can be consumed by the user or runtime.
+ /// the authentication middleware that was responsible for this ticket.
+ public AuthenticationTicket(ClaimsPrincipal principal, AuthenticationProperties properties, string authenticationScheme)
+ {
+ if (principal == null)
+ {
+ throw new ArgumentNullException(nameof(principal));
+ }
+
+ AuthenticationScheme = authenticationScheme;
+ Principal = principal;
+ Properties = properties ?? new AuthenticationProperties();
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ ///
+ /// the that represents the authenticated user.
+ /// the authentication middleware that was responsible for this ticket.
+ public AuthenticationTicket(ClaimsPrincipal principal, string authenticationScheme)
+ : this(principal, properties: null, authenticationScheme: authenticationScheme)
+ { }
+
+ ///
+ /// Gets the authentication type.
+ ///
+ public string AuthenticationScheme { get; private set; }
+
+ ///
+ /// Gets the claims-principal with authenticated user identities.
+ ///
+ public ClaimsPrincipal Principal { get; private set; }
+
+ ///
+ /// Additional state values for the authentication session.
+ ///
+ public AuthenticationProperties Properties { get; private set; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationToken.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationToken.cs
new file mode 100644
index 0000000000..555da9e098
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/AuthenticationToken.cs
@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Name/Value representing an token.
+ ///
+ public class AuthenticationToken
+ {
+ ///
+ /// Name.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Value.
+ ///
+ public string Value { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/BaseAuthenticationContext.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/BaseAuthenticationContext.cs
new file mode 100644
index 0000000000..cfe5809c5a
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/BaseAuthenticationContext.cs
@@ -0,0 +1,41 @@
+// Copyright (c) .NET Foundation. 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.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Base context for authentication.
+ ///
+ public abstract class BaseAuthenticationContext : BaseContext
+ {
+ ///
+ /// Constructor.
+ ///
+ /// The context.
+ /// The name of the scheme.
+ /// The properties.
+ protected BaseAuthenticationContext(HttpContext context, string authenticationScheme, AuthenticationProperties properties) : base(context)
+ {
+ if (string.IsNullOrEmpty(authenticationScheme))
+ {
+ throw new ArgumentException(nameof(authenticationScheme));
+ }
+
+ AuthenticationScheme = authenticationScheme;
+ Properties = properties ?? new AuthenticationProperties();
+ }
+
+ ///
+ /// The name of the scheme.
+ ///
+ public string AuthenticationScheme { get; }
+
+ ///
+ /// Contains the extra meta-data arriving with the authentication. May be altered.
+ ///
+ public AuthenticationProperties Properties { get; protected set; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/BaseContext.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/BaseContext.cs
new file mode 100644
index 0000000000..3d65f0dd75
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/BaseContext.cs
@@ -0,0 +1,49 @@
+// Copyright (c) .NET Foundation. 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.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Base class used by other context classes.
+ ///
+ public abstract class BaseContext
+ {
+ ///
+ /// Constructor.
+ ///
+ /// The request context.
+ protected BaseContext(HttpContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ HttpContext = context;
+ }
+
+ ///
+ /// The context.
+ ///
+ public HttpContext HttpContext { get; }
+
+ ///
+ /// The request.
+ ///
+ public HttpRequest Request
+ {
+ get { return HttpContext.Request; }
+ }
+
+ ///
+ /// The response.
+ ///
+ public HttpResponse Response
+ {
+ get { return HttpContext.Response; }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/ChallengeBehavior.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/ChallengeBehavior.cs
new file mode 100644
index 0000000000..1506021dd4
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/ChallengeBehavior.cs
@@ -0,0 +1,15 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Controls how challenge will behave (i.e. 401 vs 403).
+ ///
+ public enum ChallengeBehavior
+ {
+ Automatic,
+ Unauthorized,
+ Forbidden
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/ChallengeContext.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/ChallengeContext.cs
new file mode 100644
index 0000000000..ee2392eb04
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/ChallengeContext.cs
@@ -0,0 +1,45 @@
+// Copyright (c) .NET Foundation. 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.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Context used for challenges.
+ ///
+ public class ChallengeContext : BaseAuthenticationContext
+ {
+ ///
+ /// Constructor.
+ ///
+ /// The context.
+ /// The name of the scheme.
+ public ChallengeContext(HttpContext httpContext, string authenticationScheme)
+ : this(httpContext, authenticationScheme, properties: null, behavior: ChallengeBehavior.Automatic)
+ { }
+
+ ///
+ /// Constructor
+ ///
+ /// The context.
+ /// The name of the scheme.
+ /// The properties.
+ /// The challenge behavior.
+ public ChallengeContext(HttpContext httpContext, string authenticationScheme, AuthenticationProperties properties, ChallengeBehavior behavior)
+ : base(httpContext, authenticationScheme, properties)
+ {
+ if (string.IsNullOrEmpty(authenticationScheme))
+ {
+ throw new ArgumentException(nameof(authenticationScheme));
+ }
+ Behavior = behavior;
+ }
+
+ ///
+ /// The challenge behavior.
+ ///
+ public ChallengeBehavior Behavior { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationFeature.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationFeature.cs
new file mode 100644
index 0000000000..43e5a13b49
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationFeature.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Used to capture path info so redirects can be computed properly within an app.Map().
+ ///
+ public interface IAuthenticationFeature
+ {
+ ///
+ /// The original path base.
+ ///
+ PathString OriginalPathBase { get; set; }
+
+ ///
+ /// The original path.
+ ///
+ PathString OriginalPath { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationHandler.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationHandler.cs
new file mode 100644
index 0000000000..7a805f7af4
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationHandler.cs
@@ -0,0 +1,50 @@
+// Copyright (c) .NET Foundation. 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.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Created per request to handle authentication for to a particular scheme.
+ ///
+ public interface IAuthenticationHandler
+ {
+ ///
+ /// The handler should initialize anything it needs from the request and scheme here.
+ ///
+ /// The scheme.
+ /// The context.
+ ///
+ Task InitializeAsync(AuthenticationScheme scheme, HttpContext context);
+
+ ///
+ /// Authentication behavior.
+ ///
+ /// The context.
+ /// The result.
+ Task AuthenticateAsync(AuthenticateContext context);
+
+ ///
+ /// Challenge behavior.
+ ///
+ /// The context.
+ /// A task.
+ Task ChallengeAsync(ChallengeContext context);
+
+ ///
+ /// Handle sign in.
+ ///
+ /// The context.
+ /// A task.
+ Task SignInAsync(SignInContext context);
+
+ ///
+ /// Signout behavior.
+ ///
+ /// The context.
+ /// A task.
+ Task SignOutAsync(SignOutContext context);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationHandlerProvider.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationHandlerProvider.cs
new file mode 100644
index 0000000000..0507f51d61
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationHandlerProvider.cs
@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation. 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.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Provides the appropriate IAuthenticationHandler instance for the authenticationScheme and request.
+ ///
+ public interface IAuthenticationHandlerProvider
+ {
+ ///
+ /// Returns the handler instance that will be used.
+ ///
+ /// The context.
+ /// The name of the authentication scheme being handled.
+ /// The handler instance.
+ Task GetHandlerAsync(HttpContext context, string authenticationScheme);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationRequestHandler.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationRequestHandler.cs
new file mode 100644
index 0000000000..fffe08f427
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationRequestHandler.cs
@@ -0,0 +1,21 @@
+// Copyright (c) .NET Foundation. 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;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Used to determine if a handler wants to participate in request processing.
+ ///
+ public interface IAuthenticationRequestHandler : IAuthenticationHandler
+ {
+
+ ///
+ /// Returns true if request processing should stop.
+ ///
+ ///
+ Task HandleRequestAsync();
+ }
+
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationSchemeProvider.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationSchemeProvider.cs
new file mode 100644
index 0000000000..4b36abdac7
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationSchemeProvider.cs
@@ -0,0 +1,70 @@
+// Copyright (c) .NET Foundation. 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.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Responsible for managing what authenticationSchemes are supported.
+ ///
+ public interface IAuthenticationSchemeProvider
+ {
+ ///
+ /// Returns all currently registered s.
+ ///
+ /// All currently registered s.
+ Task> GetAllSchemesAsync();
+
+ ///
+ /// Returns the matching the name, or null.
+ ///
+ /// The name of the authenticationScheme.
+ /// The scheme or null if not found.
+ Task GetSchemeAsync(string name);
+
+ ///
+ /// Returns the scheme that will be used by default for .
+ /// This is typically specified via .
+ /// Otherwise, if only a single scheme exists, that will be used, if more than one exists, null will be returned.
+ ///
+ /// The scheme that will be used by default for .
+ Task GetDefaultAuthenticateSchemeAsync();
+
+ ///
+ /// Returns the scheme that will be used by default for .
+ /// This is typically specified via .
+ /// Otherwise, if only a single scheme exists, that will be used, if more than one exists, null will be returned.
+ ///
+ /// The scheme that will be used by default for .
+ Task GetDefaultChallengeSchemeAsync();
+
+ ///
+ /// Returns the scheme that will be used by default for .
+ /// This is typically specified via .
+ /// Otherwise, if only a single scheme exists, that will be used, if more than one exists, null will be returned.
+ ///
+ /// The scheme that will be used by default for .
+ Task GetDefaultSignInSchemeAsync();
+
+ ///
+ /// Registers a scheme for use by .
+ ///
+ /// The scheme.
+ void AddScheme(AuthenticationScheme scheme);
+
+ ///
+ /// Removes a scheme, preventing it from being used by .
+ ///
+ /// The name of the authenticationScheme being removed.
+ void RemoveScheme(string name);
+
+ ///
+ /// Returns the schemes in priority order for request handling.
+ ///
+ /// The schemes in priority order for request handling
+ Task> GetRequestHandlerSchemesAsync();
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationService.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationService.cs
new file mode 100644
index 0000000000..ec54325ea4
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/IAuthenticationService.cs
@@ -0,0 +1,52 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Used to provide authentication.
+ ///
+ public interface IAuthenticationService
+ {
+ ///
+ /// Authenticate for the specified authentication scheme.
+ ///
+ /// The .
+ /// The name of the authentication scheme.
+ /// The result.
+ Task AuthenticateAsync(HttpContext context, string scheme);
+
+ ///
+ /// Challenge the specified authentication scheme.
+ ///
+ /// The .
+ /// The name of the authentication scheme.
+ /// The .
+ /// The .
+ /// A task.
+ Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties, ChallengeBehavior behavior);
+
+ ///
+ /// Sign a principal in for the specified authentication scheme.
+ ///
+ /// The .
+ /// The name of the authentication scheme.
+ /// The to sign in.
+ /// The .
+ /// A task.
+ Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties);
+
+ ///
+ /// Sign out the specified authentication scheme.
+ ///
+ /// The .
+ /// The name of the authentication scheme.
+ /// The .
+ /// A task.
+ Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/IClaimsTransformation.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/IClaimsTransformation.cs
new file mode 100644
index 0000000000..3aed710aa9
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/IClaimsTransformation.cs
@@ -0,0 +1,21 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Security.Claims;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Used by the for claims transformation.
+ ///
+ public interface IClaimsTransformation
+ {
+ ///
+ /// Provides a central transformation point to change the specified principal.
+ ///
+ /// The to transform.
+ /// The transformed principal.
+ Task TransformAsync(ClaimsPrincipal principal);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/Microsoft.AspNetCore.Authentication.Abstractions.csproj b/src/Microsoft.AspNetCore.Authentication.Abstractions/Microsoft.AspNetCore.Authentication.Abstractions.csproj
new file mode 100644
index 0000000000..aa2fae7fc3
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/Microsoft.AspNetCore.Authentication.Abstractions.csproj
@@ -0,0 +1,24 @@
+
+
+
+ ASP.NET Core common types used by the various authentication components.
+ netstandard1.3;net46
+ $(NoWarn);CS1591
+ true
+ aspnetcore;authentication;security
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/SignInContext.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/SignInContext.cs
new file mode 100644
index 0000000000..e89b663a7d
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/SignInContext.cs
@@ -0,0 +1,37 @@
+// Copyright (c) .NET Foundation. 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 Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Context used for sign out.
+ ///
+ public class SignInContext : BaseAuthenticationContext
+ {
+ ///
+ /// Constructor.
+ ///
+ /// The context.
+ /// The name of the authentication scheme.
+ /// The user to sign in.
+ /// The properties.
+ public SignInContext(HttpContext context, string authenticationScheme, ClaimsPrincipal principal, AuthenticationProperties properties)
+ : base(context, authenticationScheme, properties)
+ {
+ if (principal == null)
+ {
+ throw new ArgumentNullException(nameof(principal));
+ }
+ Principal = principal;
+ }
+
+ ///
+ /// The user to sign in.
+ ///
+ public ClaimsPrincipal Principal { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/SignOutContext.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/SignOutContext.cs
new file mode 100644
index 0000000000..307a3af875
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/SignOutContext.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Context used to sign out.
+ ///
+ public class SignOutContext : BaseAuthenticationContext
+ {
+ ///
+ /// Constructor.
+ ///
+ /// The context.
+ /// The name of the authentication scheme.
+ /// The properties.
+ public SignOutContext(HttpContext context, string authenticationScheme, AuthenticationProperties properties)
+ : base(context, authenticationScheme, properties)
+ { }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Authentication.Abstractions/TokenExtensions.cs b/src/Microsoft.AspNetCore.Authentication.Abstractions/TokenExtensions.cs
new file mode 100644
index 0000000000..24f66d9043
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Abstractions/TokenExtensions.cs
@@ -0,0 +1,155 @@
+// Copyright (c) .NET Foundation. 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.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Extension methods for storing authentication tokens in .
+ ///
+ public static class AuthenticationTokenExtensions
+ {
+ private static string TokenNamesKey = ".TokenNames";
+ private static string TokenKeyPrefix = ".Token.";
+
+ ///
+ /// Stores a set of authentication tokens, after removing any old tokens.
+ ///
+ /// The properties.
+ /// The tokens to store.
+ public static void StoreTokens(this AuthenticationProperties properties, IEnumerable tokens)
+ {
+ if (properties == null)
+ {
+ throw new ArgumentNullException(nameof(properties));
+ }
+ if (tokens == null)
+ {
+ throw new ArgumentNullException(nameof(tokens));
+ }
+
+ // Clear old tokens first
+ var oldTokens = properties.GetTokens();
+ foreach (var t in oldTokens)
+ {
+ properties.Items.Remove(TokenKeyPrefix + t.Name);
+ }
+ properties.Items.Remove(TokenNamesKey);
+
+ var tokenNames = new List();
+ foreach (var token in tokens)
+ {
+ // REVIEW: should probably check that there are no ; in the token name and throw or encode
+ tokenNames.Add(token.Name);
+ properties.Items[TokenKeyPrefix+token.Name] = token.Value;
+ }
+ if (tokenNames.Count > 0)
+ {
+ properties.Items[TokenNamesKey] = string.Join(";", tokenNames.ToArray());
+ }
+ }
+
+ ///
+ /// Returns the value of a token.
+ ///
+ /// The properties.
+ /// The token name.
+ /// The token value.
+ public static string GetTokenValue(this AuthenticationProperties properties, string tokenName)
+ {
+ if (properties == null)
+ {
+ throw new ArgumentNullException(nameof(properties));
+ }
+ if (tokenName == null)
+ {
+ throw new ArgumentNullException(nameof(tokenName));
+ }
+
+ var tokenKey = TokenKeyPrefix + tokenName;
+ return properties.Items.ContainsKey(tokenKey)
+ ? properties.Items[tokenKey]
+ : null;
+ }
+
+ public static bool UpdateTokenValue(this AuthenticationProperties properties, string tokenName, string tokenValue)
+ {
+ if (properties == null)
+ {
+ throw new ArgumentNullException(nameof(properties));
+ }
+ if (tokenName == null)
+ {
+ throw new ArgumentNullException(nameof(tokenName));
+ }
+
+ var tokenKey = TokenKeyPrefix + tokenName;
+ if (!properties.Items.ContainsKey(tokenKey))
+ {
+ return false;
+ }
+ properties.Items[tokenKey] = tokenValue;
+ return true;
+ }
+
+ ///
+ /// Returns all of the AuthenticationTokens contained in the properties.
+ ///
+ /// The properties.
+ /// The authentication toekns.
+ public static IEnumerable GetTokens(this AuthenticationProperties properties)
+ {
+ if (properties == null)
+ {
+ throw new ArgumentNullException(nameof(properties));
+ }
+
+ var tokens = new List();
+ if (properties.Items.ContainsKey(TokenNamesKey))
+ {
+ var tokenNames = properties.Items[TokenNamesKey].Split(';');
+ foreach (var name in tokenNames)
+ {
+ var token = properties.GetTokenValue(name);
+ if (token != null)
+ {
+ tokens.Add(new AuthenticationToken { Name = name, Value = token });
+ }
+ }
+ }
+
+ return tokens;
+ }
+
+ ///
+ /// Extension method for getting the value of an authentication token.
+ ///
+ /// The .
+ /// The context.
+ /// The name of the authentication scheme.
+ /// The name of the token.
+ /// The value of the token.
+ public static async Task GetTokenAsync(this IAuthenticationService auth, HttpContext context, string scheme, string tokenName)
+ {
+ if (auth == null)
+ {
+ throw new ArgumentNullException(nameof(auth));
+ }
+ if (scheme == null)
+ {
+ throw new ArgumentNullException(nameof(scheme));
+ }
+ if (tokenName == null)
+ {
+ throw new ArgumentNullException(nameof(tokenName));
+ }
+
+ var result = await auth.AuthenticateAsync(context, scheme);
+ return result?.Properties?.GetTokenValue(tokenName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Authentication.Core/AuthenticationCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Authentication.Core/AuthenticationCoreServiceCollectionExtensions.cs
new file mode 100644
index 0000000000..fdf85a9b45
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Core/AuthenticationCoreServiceCollectionExtensions.cs
@@ -0,0 +1,56 @@
+// Copyright (c) .NET Foundation. 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.AspNetCore.Authentication;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ ///
+ /// Extension methods for setting up authentication services in an .
+ ///
+ public static class AuthenticationCoreServiceCollectionExtensions
+ {
+ ///
+ /// Add core authentication services needed for .
+ ///
+ /// The .
+ /// The service collection.
+ public static IServiceCollection AddAuthenticationCore(this IServiceCollection services)
+ {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ services.TryAddScoped();
+ services.TryAddSingleton(); // Can be replaced with scoped ones that use DbContext
+ services.TryAddScoped();
+ services.TryAddSingleton();
+ return services;
+ }
+
+ ///
+ /// Add core authentication services needed for .
+ ///
+ /// The .
+ /// Used to configure the .
+ /// The service collection.
+ public static IServiceCollection AddAuthenticationCore(this IServiceCollection services, Action configureOptions) {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ if (configureOptions == null)
+ {
+ throw new ArgumentNullException(nameof(configureOptions));
+ }
+
+ services.AddAuthenticationCore();
+ services.Configure(configureOptions);
+ return services;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Core/AuthenticationFeature.cs b/src/Microsoft.AspNetCore.Authentication.Core/AuthenticationFeature.cs
new file mode 100644
index 0000000000..3282cbf467
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Core/AuthenticationFeature.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Used to capture path info so redirects can be computed properly within an app.Map().
+ ///
+ public class AuthenticationFeature : IAuthenticationFeature
+ {
+ ///
+ /// The original path base.
+ ///
+ public PathString OriginalPathBase { get; set; }
+
+ ///
+ /// The original path.
+ ///
+ public PathString OriginalPath { get; set; }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Core/AuthenticationHandlerProvider.cs b/src/Microsoft.AspNetCore.Authentication.Core/AuthenticationHandlerProvider.cs
new file mode 100644
index 0000000000..c4921e5334
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Core/AuthenticationHandlerProvider.cs
@@ -0,0 +1,63 @@
+// Copyright (c) .NET Foundation. 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.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Implementation of .
+ ///
+ public class AuthenticationHandlerProvider : IAuthenticationHandlerProvider
+ {
+ ///
+ /// Constructor.
+ ///
+ /// The .
+ public AuthenticationHandlerProvider(IAuthenticationSchemeProvider schemes)
+ {
+ Schemes = schemes;
+ }
+
+ ///
+ /// The .
+ ///
+ public IAuthenticationSchemeProvider Schemes { get; }
+
+ // handler instance cache, need to initialize once per request
+ private Dictionary _handlerMap = new Dictionary(StringComparer.Ordinal);
+
+ ///
+ /// Returns the handler instance that will be used.
+ ///
+ /// The context.
+ /// The name of the authentication scheme being handled.
+ /// The handler instance.
+ public async Task GetHandlerAsync(HttpContext context, string authenticationScheme)
+ {
+ if (_handlerMap.ContainsKey(authenticationScheme))
+ {
+ return _handlerMap[authenticationScheme];
+ }
+
+ var scheme = await Schemes.GetSchemeAsync(authenticationScheme);
+ if (scheme == null)
+ {
+ return null;
+ }
+ var handler = (context.RequestServices.GetService(scheme.HandlerType) ??
+ ActivatorUtilities.CreateInstance(context.RequestServices, scheme.HandlerType))
+ as IAuthenticationHandler;
+ if (handler != null)
+ {
+ await handler.InitializeAsync(scheme, context);
+ _handlerMap[authenticationScheme] = handler;
+ }
+ return handler;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Core/AuthenticationSchemeProvider.cs b/src/Microsoft.AspNetCore.Authentication.Core/AuthenticationSchemeProvider.cs
new file mode 100644
index 0000000000..fe347d6ee9
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Core/AuthenticationSchemeProvider.cs
@@ -0,0 +1,170 @@
+// Copyright (c) .NET Foundation. 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.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Implements .
+ ///
+ public class AuthenticationSchemeProvider : IAuthenticationSchemeProvider
+ {
+ ///
+ /// Constructor.
+ ///
+ /// The options.
+ public AuthenticationSchemeProvider(IOptions options)
+ {
+ _options = options.Value;
+
+ foreach (var builder in _options.Schemes)
+ {
+ var scheme = builder.Build();
+ AddScheme(scheme);
+ }
+ }
+
+ private readonly AuthenticationOptions _options;
+ private readonly object _lock = new object();
+
+ private IDictionary _map = new Dictionary(StringComparer.Ordinal);
+ private List _requestHandlers = new List();
+
+ ///
+ /// Returns the scheme that will be used by default for .
+ /// This is typically specified via .
+ /// Otherwise, if only a single scheme exists, that will be used, if more than one exists, null will be returned.
+ ///
+ /// The scheme that will be used by default for .
+ public Task GetDefaultAuthenticateSchemeAsync()
+ {
+ if (_options.DefaultAuthenticationScheme != null)
+ {
+ return GetSchemeAsync(_options.DefaultAuthenticationScheme);
+ }
+ if (_map.Count == 1)
+ {
+ return Task.FromResult(_map.Values.First());
+ }
+ return Task.FromResult(null);
+ }
+
+ ///
+ /// Returns the scheme that will be used by default for .
+ /// This is typically specified via .
+ /// Otherwise, if only a single scheme exists, that will be used, if more than one exists, null will be returned.
+ ///
+ /// The scheme that will be used by default for .
+ public Task GetDefaultChallengeSchemeAsync()
+ {
+ if (_options.DefaultChallengeScheme != null)
+ {
+ return GetSchemeAsync(_options.DefaultChallengeScheme);
+ }
+ if (_map.Count == 1)
+ {
+ return Task.FromResult(_map.Values.First());
+ }
+ return Task.FromResult(null);
+ }
+
+ ///
+ /// Returns the scheme that will be used by default for .
+ /// This is typically specified via .
+ /// Otherwise, if only a single scheme exists, that will be used, if more than one exists, null will be returned.
+ ///
+ /// The scheme that will be used by default for .
+ public Task GetDefaultSignInSchemeAsync()
+ {
+ if (_options.DefaultSignInScheme != null)
+ {
+ return GetSchemeAsync(_options.DefaultSignInScheme);
+ }
+ if (_map.Count == 1)
+ {
+ return Task.FromResult(_map.Values.First());
+ }
+ return Task.FromResult(null);
+ }
+
+ ///
+ /// Returns the matching the name, or null.
+ ///
+ /// The name of the authenticationScheme.
+ /// The scheme or null if not found.
+ public Task GetSchemeAsync(string name)
+ {
+ if (_map.ContainsKey(name))
+ {
+ return Task.FromResult(_map[name]);
+ }
+ return Task.FromResult(null);
+ }
+
+ ///
+ /// Returns the schemes in priority order for request handling.
+ ///
+ /// The schemes in priority order for request handling
+ public Task> GetRequestHandlerSchemesAsync()
+ {
+ return Task.FromResult>(_requestHandlers);
+ }
+
+ ///
+ /// Registers a scheme for use by .
+ ///
+ /// The scheme.
+ public void AddScheme(AuthenticationScheme scheme)
+ {
+ if (_map.ContainsKey(scheme.Name))
+ {
+ throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
+ }
+ lock (_lock)
+ {
+ if (_map.ContainsKey(scheme.Name))
+ {
+ throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
+ }
+ if (typeof(IAuthenticationRequestHandler).IsAssignableFrom(scheme.HandlerType))
+ {
+ _requestHandlers.Add(scheme);
+ }
+ _map[scheme.Name] = scheme;
+ }
+ }
+
+ ///
+ /// Removes a scheme, preventing it from being used by .
+ ///
+ /// The name of the authenticationScheme being removed.
+ public void RemoveScheme(string name)
+ {
+ if (!_map.ContainsKey(name))
+ {
+ return;
+ }
+ lock (_lock)
+ {
+ if (_map.ContainsKey(name))
+ {
+ var scheme = _map[name];
+ _requestHandlers.Remove(_requestHandlers.Where(s => s.Name == name).FirstOrDefault());
+ _map.Remove(name);
+ }
+ }
+ }
+
+ public Task> GetAllSchemesAsync()
+ {
+ return Task.FromResult>(_map.Values);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Authentication.Core/AuthenticationService.cs b/src/Microsoft.AspNetCore.Authentication.Core/AuthenticationService.cs
new file mode 100644
index 0000000000..9b8837f22e
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Core/AuthenticationService.cs
@@ -0,0 +1,167 @@
+// Copyright (c) .NET Foundation. 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.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Implements .
+ ///
+ public class AuthenticationService : IAuthenticationService
+ {
+ ///
+ /// Constructor.
+ ///
+ /// The .
+ /// The .
+ /// The The .
+ public AuthenticationService(IAuthenticationSchemeProvider schemes, IAuthenticationHandlerProvider handlers, IClaimsTransformation transform)
+ {
+ Schemes = schemes;
+ Handlers = handlers;
+ Transform = transform;
+ }
+
+ ///
+ /// Used to lookup AuthenticationSchemes.
+ ///
+ public IAuthenticationSchemeProvider Schemes { get; }
+
+ ///
+ /// Used to resolve IAuthenticationHandler instances.
+ ///
+ public IAuthenticationHandlerProvider Handlers { get; }
+
+ ///
+ /// Used for claims transformation.
+ ///
+ public IClaimsTransformation Transform { get; }
+
+ ///
+ /// Authenticate for the specified authentication scheme.
+ ///
+ /// The .
+ /// The name of the authentication scheme.
+ /// The result.
+ public virtual async Task AuthenticateAsync(HttpContext context, string scheme)
+ {
+ if (scheme == null)
+ {
+ var defaultScheme = await Schemes.GetDefaultAuthenticateSchemeAsync();
+ scheme = defaultScheme?.Name;
+ if (scheme == null)
+ {
+ throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found.");
+ }
+ }
+
+ var handler = await Handlers.GetHandlerAsync(context, scheme);
+ if (handler == null)
+ {
+ throw new InvalidOperationException($"No authentication handler is configured to authenticate for the scheme: {scheme}");
+ }
+
+ var authContext = new AuthenticateContext(context, scheme);
+ var result = await handler.AuthenticateAsync(authContext);
+ if (result.Succeeded)
+ {
+ var transformed = await Transform.TransformAsync(result.Principal);
+ return AuthenticateResult.Success(new AuthenticationTicket(transformed, result.Properties, result.Ticket.AuthenticationScheme));
+ }
+ return result;
+ }
+
+ ///
+ /// Challenge the specified authentication scheme.
+ ///
+ /// The .
+ /// The name of the authentication scheme.
+ /// The .
+ /// The .
+ /// A task.
+ public virtual async Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties, ChallengeBehavior behavior)
+ {
+ if (scheme == null)
+ {
+ var defaultChallengeScheme = await Schemes.GetDefaultChallengeSchemeAsync();
+ scheme = defaultChallengeScheme?.Name;
+ if (scheme == null)
+ {
+ throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultChallengeScheme found.");
+ }
+ }
+
+ var handler = await Handlers.GetHandlerAsync(context, scheme);
+ if (handler == null)
+ {
+ throw new InvalidOperationException($"No authentication handler is configured to handle the scheme: {scheme}");
+ }
+
+ var challengeContext = new ChallengeContext(context, scheme, properties, behavior);
+ await handler.ChallengeAsync(challengeContext);
+ }
+
+ ///
+ /// Sign a principal in for the specified authentication scheme.
+ ///
+ /// The .
+ /// The name of the authentication scheme.
+ /// The to sign in.
+ /// The .
+ /// A task.
+ public virtual async Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
+ {
+ if (principal == null)
+ {
+ throw new ArgumentNullException(nameof(principal));
+ }
+
+ if (scheme == null)
+ {
+ var defaultScheme = await Schemes.GetDefaultSignInSchemeAsync();
+ scheme = defaultScheme?.Name;
+ if (scheme == null)
+ {
+ throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultSignInScheme found.");
+ }
+ }
+
+ var handler = await Handlers.GetHandlerAsync(context, scheme);
+ if (handler == null)
+ {
+ throw new InvalidOperationException($"No authentication handler is configured to handle the scheme: {scheme}");
+ }
+
+ var signInContext = new SignInContext(context, scheme, principal, properties);
+ await handler.SignInAsync(signInContext);
+ }
+
+ ///
+ /// Sign out the specified authentication scheme.
+ ///
+ /// The .
+ /// The name of the authentication scheme.
+ /// The .
+ /// A task.
+ public virtual async Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties)
+ {
+ if (string.IsNullOrEmpty(scheme))
+ {
+ throw new ArgumentException(nameof(scheme));
+ }
+
+ var handler = await Handlers.GetHandlerAsync(context, scheme);
+ if (handler == null)
+ {
+ throw new InvalidOperationException($"No authentication handler is configured to handle the scheme: {scheme}");
+ }
+
+ var signOutContext = new SignOutContext(context, scheme, properties);
+ await handler.SignOutAsync(signOutContext);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Authentication.Core/Microsoft.AspNetCore.Authentication.Core.csproj b/src/Microsoft.AspNetCore.Authentication.Core/Microsoft.AspNetCore.Authentication.Core.csproj
new file mode 100644
index 0000000000..61628336f1
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Core/Microsoft.AspNetCore.Authentication.Core.csproj
@@ -0,0 +1,21 @@
+
+
+
+
+
+ ASP.NET Core common types used by the various authentication middleware components.
+ netstandard1.3;net46
+ $(NoWarn);CS1591
+ true
+ aspnetcore;authentication;security
+ false
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.AspNetCore.Authentication.Core/NoopClaimsTransformation.cs b/src/Microsoft.AspNetCore.Authentication.Core/NoopClaimsTransformation.cs
new file mode 100644
index 0000000000..83c488fe42
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Authentication.Core/NoopClaimsTransformation.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Security.Claims;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Default claims transformation is a no-op.
+ ///
+ public class NoopClaimsTransformation : IClaimsTransformation
+ {
+ ///
+ /// Returns the principal unchanged.
+ ///
+ /// The user.
+ /// The principal unchanged.
+ public virtual Task TransformAsync(ClaimsPrincipal principal)
+ {
+ return Task.FromResult(principal);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Authentication.Core.Test/Microsoft.AspNetCore.Authentication.Core.Test.csproj b/test/Microsoft.AspNetCore.Authentication.Core.Test/Microsoft.AspNetCore.Authentication.Core.Test.csproj
new file mode 100644
index 0000000000..925f5aa079
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Authentication.Core.Test/Microsoft.AspNetCore.Authentication.Core.Test.csproj
@@ -0,0 +1,19 @@
+
+
+
+
+ netcoreapp2.0;net46
+ netcoreapp2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Microsoft.AspNetCore.Authentication.Core.Test/TokenExtensionTests.cs b/test/Microsoft.AspNetCore.Authentication.Core.Test/TokenExtensionTests.cs
new file mode 100644
index 0000000000..3e3eb9c52b
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Authentication.Core.Test/TokenExtensionTests.cs
@@ -0,0 +1,123 @@
+// Copyright (c) .NET Foundation. 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 Xunit;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ public class TokenExtensionTests
+ {
+ [Fact]
+ public void CanStoreMultipleTokens()
+ {
+ var props = new AuthenticationProperties();
+ var tokens = new List();
+ var tok1 = new AuthenticationToken { Name = "One", Value = "1" };
+ var tok2 = new AuthenticationToken { Name = "Two", Value = "2" };
+ var tok3 = new AuthenticationToken { Name = "Three", Value = "3" };
+ tokens.Add(tok1);
+ tokens.Add(tok2);
+ tokens.Add(tok3);
+ props.StoreTokens(tokens);
+
+ Assert.Equal("1", props.GetTokenValue("One"));
+ Assert.Equal("2", props.GetTokenValue("Two"));
+ Assert.Equal("3", props.GetTokenValue("Three"));
+ Assert.Equal(3, props.GetTokens().Count());
+ }
+
+ [Fact]
+ public void SubsequentStoreTokenDeletesPreviousTokens()
+ {
+ var props = new AuthenticationProperties();
+ var tokens = new List();
+ var tok1 = new AuthenticationToken { Name = "One", Value = "1" };
+ var tok2 = new AuthenticationToken { Name = "Two", Value = "2" };
+ var tok3 = new AuthenticationToken { Name = "Three", Value = "3" };
+ tokens.Add(tok1);
+ tokens.Add(tok2);
+ tokens.Add(tok3);
+
+ props.StoreTokens(tokens);
+
+ props.StoreTokens(new[] { new AuthenticationToken { Name = "Zero", Value = "0" } });
+
+ Assert.Equal("0", props.GetTokenValue("Zero"));
+ Assert.Equal(null, props.GetTokenValue("One"));
+ Assert.Equal(null, props.GetTokenValue("Two"));
+ Assert.Equal(null, props.GetTokenValue("Three"));
+ Assert.Equal(1, props.GetTokens().Count());
+ }
+
+ [Fact]
+ public void CanUpdateTokens()
+ {
+ var props = new AuthenticationProperties();
+ var tokens = new List();
+ var tok1 = new AuthenticationToken { Name = "One", Value = "1" };
+ var tok2 = new AuthenticationToken { Name = "Two", Value = "2" };
+ var tok3 = new AuthenticationToken { Name = "Three", Value = "3" };
+ tokens.Add(tok1);
+ tokens.Add(tok2);
+ tokens.Add(tok3);
+ props.StoreTokens(tokens);
+
+ tok1.Value = ".1";
+ tok2.Value = ".2";
+ tok3.Value = ".3";
+ props.StoreTokens(tokens);
+
+ Assert.Equal(".1", props.GetTokenValue("One"));
+ Assert.Equal(".2", props.GetTokenValue("Two"));
+ Assert.Equal(".3", props.GetTokenValue("Three"));
+ Assert.Equal(3, props.GetTokens().Count());
+ }
+
+ [Fact]
+ public void CanUpdateTokenValues()
+ {
+ var props = new AuthenticationProperties();
+ var tokens = new List();
+ var tok1 = new AuthenticationToken { Name = "One", Value = "1" };
+ var tok2 = new AuthenticationToken { Name = "Two", Value = "2" };
+ var tok3 = new AuthenticationToken { Name = "Three", Value = "3" };
+ tokens.Add(tok1);
+ tokens.Add(tok2);
+ tokens.Add(tok3);
+ props.StoreTokens(tokens);
+
+ Assert.True(props.UpdateTokenValue("One", ".11"));
+ Assert.True(props.UpdateTokenValue("Two", ".22"));
+ Assert.True(props.UpdateTokenValue("Three", ".33"));
+
+ Assert.Equal(".11", props.GetTokenValue("One"));
+ Assert.Equal(".22", props.GetTokenValue("Two"));
+ Assert.Equal(".33", props.GetTokenValue("Three"));
+ Assert.Equal(3, props.GetTokens().Count());
+ }
+
+ [Fact]
+ public void UpdateTokenValueReturnsFalseForUnknownToken()
+ {
+ var props = new AuthenticationProperties();
+ var tokens = new List();
+ var tok1 = new AuthenticationToken { Name = "One", Value = "1" };
+ var tok2 = new AuthenticationToken { Name = "Two", Value = "2" };
+ var tok3 = new AuthenticationToken { Name = "Three", Value = "3" };
+ tokens.Add(tok1);
+ tokens.Add(tok2);
+ tokens.Add(tok3);
+ props.StoreTokens(tokens);
+
+ Assert.False(props.UpdateTokenValue("ONE", ".11"));
+ Assert.False(props.UpdateTokenValue("Jigglypuff", ".11"));
+
+ Assert.Null(props.GetTokenValue("ONE"));
+ Assert.Null(props.GetTokenValue("Jigglypuff"));
+ Assert.Equal(3, props.GetTokens().Count());
+
+ }
+ }
+}