diff --git a/src/Http/Authentication.Abstractions/src/AuthenticateResult.cs b/src/Http/Authentication.Abstractions/src/AuthenticateResult.cs
new file mode 100644
index 0000000000..5982143bcb
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/AuthenticateResult.cs
@@ -0,0 +1,107 @@
+// 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
+ {
+ protected AuthenticateResult() { }
+
+ ///
+ /// If a ticket was produced, authenticate was successful.
+ ///
+ public bool Succeeded => Ticket != null;
+
+ ///
+ /// The authentication ticket.
+ ///
+ public AuthenticationTicket Ticket { get; protected set; }
+
+ ///
+ /// Gets the claims-principal with authenticated user identities.
+ ///
+ public ClaimsPrincipal Principal => Ticket?.Principal;
+
+ ///
+ /// Additional state values for the authentication session.
+ ///
+ public AuthenticationProperties Properties { get; protected set; }
+
+ ///
+ /// Holds failure information from the authentication.
+ ///
+ public Exception Failure { get; protected set; }
+
+ ///
+ /// Indicates that there was no information returned for this authentication scheme.
+ ///
+ public bool None { get; protected 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, Properties = ticket.Properties };
+ }
+
+ ///
+ /// Indicates that there was no information returned for this authentication scheme.
+ ///
+ /// The result.
+ public static AuthenticateResult NoResult()
+ {
+ return new AuthenticateResult() { None = 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 exception.
+ /// Additional state values for the authentication session.
+ /// The result.
+ public static AuthenticateResult Fail(Exception failure, AuthenticationProperties properties)
+ {
+ return new AuthenticateResult() { Failure = failure, Properties = properties };
+ }
+
+ ///
+ /// Indicates that there was a failure during authentication.
+ ///
+ /// The failure message.
+ /// The result.
+ public static AuthenticateResult Fail(string failureMessage)
+ => Fail(new Exception(failureMessage));
+
+ ///
+ /// Indicates that there was a failure during authentication.
+ ///
+ /// The failure message.
+ /// Additional state values for the authentication session.
+ /// The result.
+ public static AuthenticateResult Fail(string failureMessage, AuthenticationProperties properties)
+ => Fail(new Exception(failureMessage), properties);
+ }
+}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationHttpContextExtensions.cs b/src/Http/Authentication.Abstractions/src/AuthenticationHttpContextExtensions.cs
new file mode 100644
index 0000000000..bb50c6534f
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationHttpContextExtensions.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.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 authenticate using the scheme.
+ ///
+ /// The context.
+ /// The properties.
+ /// The task.
+ public static Task ChallengeAsync(this HttpContext context, AuthenticationProperties properties) =>
+ context.ChallengeAsync(scheme: null, properties: properties);
+
+ ///
+ /// 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.RequestServices.GetRequiredService().ChallengeAsync(context, scheme, properties);
+
+ ///
+ /// 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 using the scheme..
+ ///
+ /// The context.
+ /// The task.
+ public static Task ForbidAsync(this HttpContext context) =>
+ context.ForbidAsync(scheme: null, properties: null);
+
+ ///
+ /// Extension method for Forbid.
+ ///
+ /// The context.
+ /// The properties.
+ /// The task.
+ public static Task ForbidAsync(this HttpContext context, AuthenticationProperties properties) =>
+ context.ForbidAsync(scheme: null, properties: properties);
+
+ ///
+ /// 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().ForbidAsync(context, scheme, properties);
+
+ ///
+ /// 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 using the .
+ ///
+ /// The context.
+ /// The task.
+ public static Task SignOutAsync(this HttpContext context) => context.SignOutAsync(scheme: null, properties: null);
+
+ ///
+ /// Extension method for SignOut using the .
+ ///
+ /// The context.
+ /// The properties.
+ /// The task.
+ public static Task SignOutAsync(this HttpContext context, AuthenticationProperties properties) => context.SignOutAsync(scheme: null, properties: 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);
+
+ ///
+ /// Extension method for getting the value of an authentication token.
+ ///
+ /// The context.
+ /// The name of the token.
+ /// The value of the token.
+ public static Task GetTokenAsync(this HttpContext context, string tokenName) =>
+ context.RequestServices.GetRequiredService().GetTokenAsync(context, tokenName);
+ }
+}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationOptions.cs b/src/Http/Authentication.Abstractions/src/AuthenticationOptions.cs
new file mode 100644
index 0000000000..2781a35757
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationOptions.cs
@@ -0,0 +1,93 @@
+// 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;
+ }
+
+ ///
+ /// Adds an .
+ ///
+ /// The responsible for the scheme.
+ /// The name of the scheme being added.
+ /// The display name for the scheme.
+ public void AddScheme(string name, string displayName) where THandler : IAuthenticationHandler
+ => AddScheme(name, b =>
+ {
+ b.DisplayName = displayName;
+ b.HandlerType = typeof(THandler);
+ });
+
+ ///
+ /// Used as the fallback default scheme for all the other defaults.
+ ///
+ public string DefaultScheme { get; set; }
+
+ ///
+ /// Used as the default scheme by .
+ ///
+ public string DefaultAuthenticateScheme { get; set; }
+
+ ///
+ /// Used as the default scheme by .
+ ///
+ public string DefaultSignInScheme { get; set; }
+
+ ///
+ /// Used as the default scheme by .
+ ///
+ public string DefaultSignOutScheme { get; set; }
+
+ ///
+ /// Used as the default scheme by .
+ ///
+ public string DefaultChallengeScheme { get; set; }
+
+ ///
+ /// Used as the default scheme by .
+ ///
+ public string DefaultForbidScheme { get; set; }
+ }
+}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationProperties.cs b/src/Http/Authentication.Abstractions/src/AuthenticationProperties.cs
new file mode 100644
index 0000000000..271329209a
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationProperties.cs
@@ -0,0 +1,212 @@
+// 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, parameters: null)
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// State values dictionary to use.
+ public AuthenticationProperties(IDictionary items)
+ : this(items, parameters: null)
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// State values dictionary to use.
+ /// Parameters dictionary to use.
+ public AuthenticationProperties(IDictionary items, IDictionary parameters)
+ {
+ Items = items ?? new Dictionary(StringComparer.Ordinal);
+ Parameters = parameters ?? new Dictionary(StringComparer.Ordinal);
+ }
+
+ ///
+ /// State values about the authentication session.
+ ///
+ public IDictionary Items { get; }
+
+ ///
+ /// Collection of parameters that are passed to the authentication handler. These are not intended for
+ /// serialization or persistence, only for flowing data between call sites.
+ ///
+ public IDictionary Parameters { get; }
+
+ ///
+ /// Gets or sets whether the authentication session is persisted across multiple requests.
+ ///
+ public bool IsPersistent
+ {
+ get => GetString(IsPersistentKey) != null;
+ set => SetString(IsPersistentKey, value ? string.Empty : null);
+ }
+
+ ///
+ /// Gets or sets the full path or absolute URI to be used as an http redirect response value.
+ ///
+ public string RedirectUri
+ {
+ get => GetString(RedirectUriKey);
+ set => SetString(RedirectUriKey, value);
+ }
+
+ ///
+ /// Gets or sets the time at which the authentication ticket was issued.
+ ///
+ public DateTimeOffset? IssuedUtc
+ {
+ get => GetDateTimeOffset(IssuedUtcKey);
+ set => SetDateTimeOffset(IssuedUtcKey, value);
+ }
+
+ ///
+ /// Gets or sets the time at which the authentication ticket expires.
+ ///
+ public DateTimeOffset? ExpiresUtc
+ {
+ get => GetDateTimeOffset(ExpiresUtcKey);
+ set => SetDateTimeOffset(ExpiresUtcKey, value);
+ }
+
+ ///
+ /// Gets or sets if refreshing the authentication session should be allowed.
+ ///
+ public bool? AllowRefresh
+ {
+ get => GetBool(RefreshKey);
+ set => SetBool(RefreshKey, value);
+ }
+
+ ///
+ /// Get a string value from the collection.
+ ///
+ /// Property key.
+ /// Retrieved value or null if the property is not set.
+ public string GetString(string key)
+ {
+ return Items.TryGetValue(key, out string value) ? value : null;
+ }
+
+ ///
+ /// Set a string value in the collection.
+ ///
+ /// Property key.
+ /// Value to set or null to remove the property.
+ public void SetString(string key, string value)
+ {
+ if (value != null)
+ {
+ Items[key] = value;
+ }
+ else if (Items.ContainsKey(key))
+ {
+ Items.Remove(key);
+ }
+ }
+
+ ///
+ /// Get a parameter from the collection.
+ ///
+ /// Parameter type.
+ /// Parameter key.
+ /// Retrieved value or the default value if the property is not set.
+ public T GetParameter(string key)
+ => Parameters.TryGetValue(key, out var obj) && obj is T value ? value : default;
+
+ ///
+ /// Set a parameter value in the collection.
+ ///
+ /// Parameter type.
+ /// Parameter key.
+ /// Value to set.
+ public void SetParameter(string key, T value)
+ => Parameters[key] = value;
+
+ ///
+ /// Get a bool value from the collection.
+ ///
+ /// Property key.
+ /// Retrieved value or null if the property is not set.
+ protected bool? GetBool(string key)
+ {
+ if (Items.TryGetValue(key, out string value) && bool.TryParse(value, out bool boolValue))
+ {
+ return boolValue;
+ }
+ return null;
+ }
+
+ ///
+ /// Set a bool value in the collection.
+ ///
+ /// Property key.
+ /// Value to set or null to remove the property.
+ protected void SetBool(string key, bool? value)
+ {
+ if (value.HasValue)
+ {
+ Items[key] = value.Value.ToString();
+ }
+ else if (Items.ContainsKey(key))
+ {
+ Items.Remove(key);
+ }
+ }
+
+ ///
+ /// Get a DateTimeOffset value from the collection.
+ ///
+ /// Property key.
+ /// Retrieved value or null if the property is not set.
+ protected DateTimeOffset? GetDateTimeOffset(string key)
+ {
+ if (Items.TryGetValue(key, out string value)
+ && DateTimeOffset.TryParseExact(value, UtcDateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTimeOffset dateTimeOffset))
+ {
+ return dateTimeOffset;
+ }
+ return null;
+ }
+
+ ///
+ /// Set a DateTimeOffset value in the collection.
+ ///
+ /// Property key.
+ /// Value to set or null to remove the property.
+ protected void SetDateTimeOffset(string key, DateTimeOffset? value)
+ {
+ if (value.HasValue)
+ {
+ Items[key] = value.Value.ToString(UtcDateTimeFormat, CultureInfo.InvariantCulture);
+ }
+ else if (Items.ContainsKey(key))
+ {
+ Items.Remove(key);
+ }
+ }
+ }
+}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationScheme.cs b/src/Http/Authentication.Abstractions/src/AuthenticationScheme.cs
new file mode 100644
index 0000000000..a72dc893ed
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationScheme.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.Reflection;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// AuthenticationSchemes assign a name to a specific
+ /// handlerType.
+ ///
+ public class AuthenticationScheme
+ {
+ ///
+ /// Constructor.
+ ///
+ /// The name for the authentication scheme.
+ /// The display name for the authentication scheme.
+ /// The type that handles this scheme.
+ public AuthenticationScheme(string name, string displayName, 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 IAuthenticationHandler.");
+ }
+
+ Name = name;
+ HandlerType = handlerType;
+ DisplayName = displayName;
+ }
+
+ ///
+ /// The name of the authentication scheme.
+ ///
+ public string Name { get; }
+
+ ///
+ /// The display name for the scheme. Null is valid and used for non user facing schemes.
+ ///
+ public string DisplayName { get; }
+
+ ///
+ /// The type that handles this scheme.
+ ///
+ public Type HandlerType { get; }
+ }
+}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationSchemeBuilder.cs b/src/Http/Authentication.Abstractions/src/AuthenticationSchemeBuilder.cs
new file mode 100644
index 0000000000..30e843c028
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationSchemeBuilder.cs
@@ -0,0 +1,43 @@
+// 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 display name for the scheme being built.
+ ///
+ public string DisplayName { get; set; }
+
+ ///
+ /// The type responsible for this scheme.
+ ///
+ public Type HandlerType { get; set; }
+
+ ///
+ /// Builds the instance.
+ ///
+ ///
+ public AuthenticationScheme Build() => new AuthenticationScheme(Name, DisplayName, HandlerType);
+ }
+}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationTicket.cs b/src/Http/Authentication.Abstractions/src/AuthenticationTicket.cs
new file mode 100644
index 0000000000..c31f15ec01
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/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/Http/Authentication.Abstractions/src/AuthenticationToken.cs b/src/Http/Authentication.Abstractions/src/AuthenticationToken.cs
new file mode 100644
index 0000000000..555da9e098
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/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/Http/Authentication.Abstractions/src/IAuthenticationFeature.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationFeature.cs
new file mode 100644
index 0000000000..43e5a13b49
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/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/Http/Authentication.Abstractions/src/IAuthenticationHandler.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationHandler.cs
new file mode 100644
index 0000000000..aeb373e18e
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationHandler.cs
@@ -0,0 +1,42 @@
+// 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 result.
+ Task AuthenticateAsync();
+
+ ///
+ /// Challenge behavior.
+ ///
+ /// The that contains the extra meta-data arriving with the authentication.
+ /// A task.
+ Task ChallengeAsync(AuthenticationProperties properties);
+
+ ///
+ /// Forbid behavior.
+ ///
+ /// The that contains the extra meta-data arriving with the authentication.
+ /// A task.
+ Task ForbidAsync(AuthenticationProperties properties);
+ }
+}
diff --git a/src/Http/Authentication.Abstractions/src/IAuthenticationHandlerProvider.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationHandlerProvider.cs
new file mode 100644
index 0000000000..0507f51d61
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/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/Http/Authentication.Abstractions/src/IAuthenticationRequestHandler.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationRequestHandler.cs
new file mode 100644
index 0000000000..fb1b227ad7
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationRequestHandler.cs
@@ -0,0 +1,20 @@
+// 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/Http/Authentication.Abstractions/src/IAuthenticationSchemeProvider.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationSchemeProvider.cs
new file mode 100644
index 0000000000..3d2584fca8
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationSchemeProvider.cs
@@ -0,0 +1,86 @@
+// 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, this will fallback to .
+ ///
+ /// 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, this will fallback to .
+ ///
+ /// 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, this will fallback to .
+ ///
+ /// The scheme that will be used by default for .
+ Task GetDefaultForbidSchemeAsync();
+
+ ///
+ /// Returns the scheme that will be used by default for .
+ /// This is typically specified via .
+ /// Otherwise, this will fallback to .
+ ///
+ /// The scheme that will be used by default for .
+ Task GetDefaultSignInSchemeAsync();
+
+ ///
+ /// Returns the scheme that will be used by default for .
+ /// This is typically specified via .
+ /// Otherwise, this will fallback to .
+ ///
+ /// The scheme that will be used by default for .
+ Task GetDefaultSignOutSchemeAsync();
+
+ ///
+ /// 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/Http/Authentication.Abstractions/src/IAuthenticationService.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationService.cs
new file mode 100644
index 0000000000..e5d5336016
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationService.cs
@@ -0,0 +1,60 @@
+// 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 .
+ /// A task.
+ Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties);
+
+ ///
+ /// Forbids the specified authentication scheme.
+ ///
+ /// The .
+ /// The name of the authentication scheme.
+ /// The .
+ /// A task.
+ Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties);
+
+ ///
+ /// 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/Http/Authentication.Abstractions/src/IAuthenticationSignInHandler.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationSignInHandler.cs
new file mode 100644
index 0000000000..69b88032d5
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationSignInHandler.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.Security.Claims;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Used to determine if a handler supports SignIn.
+ ///
+ public interface IAuthenticationSignInHandler : IAuthenticationSignOutHandler
+ {
+ ///
+ /// Handle sign in.
+ ///
+ /// The user.
+ /// The that contains the extra meta-data arriving with the authentication.
+ /// A task.
+ Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties);
+ }
+}
diff --git a/src/Http/Authentication.Abstractions/src/IAuthenticationSignOutHandler.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationSignOutHandler.cs
new file mode 100644
index 0000000000..f76d116a76
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationSignOutHandler.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 supports SignOut.
+ ///
+ public interface IAuthenticationSignOutHandler : IAuthenticationHandler
+ {
+ ///
+ /// Signout behavior.
+ ///
+ /// The that contains the extra meta-data arriving with the authentication.
+ /// A task.
+ Task SignOutAsync(AuthenticationProperties properties);
+ }
+
+}
diff --git a/src/Http/Authentication.Abstractions/src/IClaimsTransformation.cs b/src/Http/Authentication.Abstractions/src/IClaimsTransformation.cs
new file mode 100644
index 0000000000..0193d95783
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/IClaimsTransformation.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 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.
+ /// Note: this will be run on each AuthenticateAsync call, so its safer to
+ /// return a new ClaimsPrincipal if your transformation is not idempotent.
+ ///
+ /// The to transform.
+ /// The transformed principal.
+ Task TransformAsync(ClaimsPrincipal principal);
+ }
+}
\ No newline at end of file
diff --git a/src/Http/Authentication.Abstractions/src/Microsoft.AspNetCore.Authentication.Abstractions.csproj b/src/Http/Authentication.Abstractions/src/Microsoft.AspNetCore.Authentication.Abstractions.csproj
new file mode 100644
index 0000000000..bfb6e8e9ed
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/Microsoft.AspNetCore.Authentication.Abstractions.csproj
@@ -0,0 +1,17 @@
+
+
+ ASP.NET Core common types used by the various authentication components.
+ netstandard2.0
+ $(NoWarn);CS1591
+ true
+ aspnetcore;authentication;security
+ false
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Http/Authentication.Abstractions/src/TokenExtensions.cs b/src/Http/Authentication.Abstractions/src/TokenExtensions.cs
new file mode 100644
index 0000000000..497acabc23
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/TokenExtensions.cs
@@ -0,0 +1,161 @@
+// 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 token.
+ /// The value of the token.
+ public static Task GetTokenAsync(this IAuthenticationService auth, HttpContext context, string tokenName)
+ => auth.GetTokenAsync(context, scheme: null, tokenName: tokenName);
+
+ ///
+ /// 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 (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/Http/Authentication.Abstractions/src/baseline.netcore.json b/src/Http/Authentication.Abstractions/src/baseline.netcore.json
new file mode 100644
index 0000000000..2d1e7e00e4
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/baseline.netcore.json
@@ -0,0 +1,1734 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.Authentication.Abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.AuthenticateResult",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Succeeded",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Ticket",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticationTicket",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_Ticket",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationTicket"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Principal",
+ "Parameters": [],
+ "ReturnType": "System.Security.Claims.ClaimsPrincipal",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Properties",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticationProperties",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Failure",
+ "Parameters": [],
+ "ReturnType": "System.Exception",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_Failure",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Exception"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_None",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_None",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Boolean"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Protected",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Success",
+ "Parameters": [
+ {
+ "Name": "ticket",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationTicket"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticateResult",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "NoResult",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticateResult",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Fail",
+ "Parameters": [
+ {
+ "Name": "failure",
+ "Type": "System.Exception"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticateResult",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Fail",
+ "Parameters": [
+ {
+ "Name": "failureMessage",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticateResult",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Protected",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "AuthenticateAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AuthenticateAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ChallengeAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ChallengeAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ChallengeAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ChallengeAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ForbidAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ForbidAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ForbidAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ForbidAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "SignInAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ },
+ {
+ "Name": "principal",
+ "Type": "System.Security.Claims.ClaimsPrincipal"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "SignInAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "principal",
+ "Type": "System.Security.Claims.ClaimsPrincipal"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "SignInAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "principal",
+ "Type": "System.Security.Claims.ClaimsPrincipal"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "SignInAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ },
+ {
+ "Name": "principal",
+ "Type": "System.Security.Claims.ClaimsPrincipal"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "SignOutAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "SignOutAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "SignOutAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "SignOutAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetTokenAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ },
+ {
+ "Name": "tokenName",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetTokenAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "tokenName",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.AuthenticationOptions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Schemes",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IEnumerable",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_SchemeMap",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IDictionary",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AddScheme",
+ "Parameters": [
+ {
+ "Name": "name",
+ "Type": "System.String"
+ },
+ {
+ "Name": "configureBuilder",
+ "Type": "System.Action"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AddScheme",
+ "Parameters": [
+ {
+ "Name": "name",
+ "Type": "System.String"
+ },
+ {
+ "Name": "displayName",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "THandler",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": [
+ "Microsoft.AspNetCore.Authentication.IAuthenticationHandler"
+ ]
+ }
+ ]
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_DefaultScheme",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_DefaultScheme",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_DefaultAuthenticateScheme",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_DefaultAuthenticateScheme",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_DefaultSignInScheme",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_DefaultSignInScheme",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_DefaultSignOutScheme",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_DefaultSignOutScheme",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_DefaultChallengeScheme",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_DefaultChallengeScheme",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_DefaultForbidScheme",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_DefaultForbidScheme",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.AuthenticationProperties",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Items",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IDictionary",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_IsPersistent",
+ "Parameters": [],
+ "ReturnType": "System.Boolean",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_IsPersistent",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Boolean"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_RedirectUri",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_RedirectUri",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_IssuedUtc",
+ "Parameters": [],
+ "ReturnType": "System.Nullable",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_IssuedUtc",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Nullable"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_ExpiresUtc",
+ "Parameters": [],
+ "ReturnType": "System.Nullable",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_ExpiresUtc",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Nullable"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_AllowRefresh",
+ "Parameters": [],
+ "ReturnType": "System.Nullable",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_AllowRefresh",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Nullable"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "items",
+ "Type": "System.Collections.Generic.IDictionary"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.AuthenticationScheme",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Name",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_DisplayName",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_HandlerType",
+ "Parameters": [],
+ "ReturnType": "System.Type",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "name",
+ "Type": "System.String"
+ },
+ {
+ "Name": "displayName",
+ "Type": "System.String"
+ },
+ {
+ "Name": "handlerType",
+ "Type": "System.Type"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.AuthenticationSchemeBuilder",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Name",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_DisplayName",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_DisplayName",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_HandlerType",
+ "Parameters": [],
+ "ReturnType": "System.Type",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_HandlerType",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Type"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Build",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticationScheme",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "name",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.AuthenticationTicket",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_AuthenticationScheme",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Principal",
+ "Parameters": [],
+ "ReturnType": "System.Security.Claims.ClaimsPrincipal",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Properties",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticationProperties",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "principal",
+ "Type": "System.Security.Claims.ClaimsPrincipal"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ },
+ {
+ "Name": "authenticationScheme",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "principal",
+ "Type": "System.Security.Claims.ClaimsPrincipal"
+ },
+ {
+ "Name": "authenticationScheme",
+ "Type": "System.String"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.AuthenticationToken",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Name",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_Name",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Value",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_Value",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.IAuthenticationFeature",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_OriginalPathBase",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_OriginalPathBase",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Microsoft.AspNetCore.Http.PathString"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_OriginalPath",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_OriginalPath",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Microsoft.AspNetCore.Http.PathString"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.IAuthenticationHandler",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "InitializeAsync",
+ "Parameters": [
+ {
+ "Name": "scheme",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationScheme"
+ },
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AuthenticateAsync",
+ "Parameters": [],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ChallengeAsync",
+ "Parameters": [
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ForbidAsync",
+ "Parameters": [
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.IAuthenticationHandlerProvider",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "GetHandlerAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "authenticationScheme",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.IAuthenticationRequestHandler",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Authentication.IAuthenticationHandler"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "HandleRequestAsync",
+ "Parameters": [],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "GetAllSchemesAsync",
+ "Parameters": [],
+ "ReturnType": "System.Threading.Tasks.Task>",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetSchemeAsync",
+ "Parameters": [
+ {
+ "Name": "name",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetDefaultAuthenticateSchemeAsync",
+ "Parameters": [],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetDefaultChallengeSchemeAsync",
+ "Parameters": [],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetDefaultForbidSchemeAsync",
+ "Parameters": [],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetDefaultSignInSchemeAsync",
+ "Parameters": [],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetDefaultSignOutSchemeAsync",
+ "Parameters": [],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AddScheme",
+ "Parameters": [
+ {
+ "Name": "scheme",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationScheme"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "RemoveScheme",
+ "Parameters": [
+ {
+ "Name": "name",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetRequestHandlerSchemesAsync",
+ "Parameters": [],
+ "ReturnType": "System.Threading.Tasks.Task>",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.IAuthenticationService",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "AuthenticateAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ChallengeAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ForbidAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "SignInAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ },
+ {
+ "Name": "principal",
+ "Type": "System.Security.Claims.ClaimsPrincipal"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "SignOutAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.IAuthenticationSignInHandler",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Authentication.IAuthenticationSignOutHandler"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "SignInAsync",
+ "Parameters": [
+ {
+ "Name": "user",
+ "Type": "System.Security.Claims.ClaimsPrincipal"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.IAuthenticationSignOutHandler",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Authentication.IAuthenticationHandler"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "SignOutAsync",
+ "Parameters": [
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.IClaimsTransformation",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "TransformAsync",
+ "Parameters": [
+ {
+ "Name": "principal",
+ "Type": "System.Security.Claims.ClaimsPrincipal"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.AuthenticationTokenExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "StoreTokens",
+ "Parameters": [
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ },
+ {
+ "Name": "tokens",
+ "Type": "System.Collections.Generic.IEnumerable"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetTokenValue",
+ "Parameters": [
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ },
+ {
+ "Name": "tokenName",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.String",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "UpdateTokenValue",
+ "Parameters": [
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ },
+ {
+ "Name": "tokenName",
+ "Type": "System.String"
+ },
+ {
+ "Name": "tokenValue",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Boolean",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetTokens",
+ "Parameters": [
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Collections.Generic.IEnumerable",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetTokenAsync",
+ "Parameters": [
+ {
+ "Name": "auth",
+ "Type": "Microsoft.AspNetCore.Authentication.IAuthenticationService"
+ },
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "tokenName",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetTokenAsync",
+ "Parameters": [
+ {
+ "Name": "auth",
+ "Type": "Microsoft.AspNetCore.Authentication.IAuthenticationService"
+ },
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ },
+ {
+ "Name": "tokenName",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Http/Authentication.Core/src/AuthenticationCoreServiceCollectionExtensions.cs b/src/Http/Authentication.Core/src/AuthenticationCoreServiceCollectionExtensions.cs
new file mode 100644
index 0000000000..fdf85a9b45
--- /dev/null
+++ b/src/Http/Authentication.Core/src/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/Http/Authentication.Core/src/AuthenticationFeature.cs b/src/Http/Authentication.Core/src/AuthenticationFeature.cs
new file mode 100644
index 0000000000..3282cbf467
--- /dev/null
+++ b/src/Http/Authentication.Core/src/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/Http/Authentication.Core/src/AuthenticationHandlerProvider.cs b/src/Http/Authentication.Core/src/AuthenticationHandlerProvider.cs
new file mode 100644
index 0000000000..c4921e5334
--- /dev/null
+++ b/src/Http/Authentication.Core/src/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/Http/Authentication.Core/src/AuthenticationSchemeProvider.cs b/src/Http/Authentication.Core/src/AuthenticationSchemeProvider.cs
new file mode 100644
index 0000000000..050118d3c4
--- /dev/null
+++ b/src/Http/Authentication.Core/src/AuthenticationSchemeProvider.cs
@@ -0,0 +1,176 @@
+// 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.Options;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ ///
+ /// Implements .
+ ///
+ public class AuthenticationSchemeProvider : IAuthenticationSchemeProvider
+ {
+ ///
+ /// Creates an instance of
+ /// using the specified ,
+ ///
+ /// The options.
+ public AuthenticationSchemeProvider(IOptions options)
+ : this(options, new Dictionary(StringComparer.Ordinal))
+ {
+ }
+
+ ///
+ /// Creates an instance of
+ /// using the specified and .
+ ///
+ /// The options.
+ /// The dictionary used to store authentication schemes.
+ protected AuthenticationSchemeProvider(IOptions options, IDictionary schemes)
+ {
+ _options = options.Value;
+
+ _schemes = schemes ?? throw new ArgumentNullException(nameof(schemes));
+ _requestHandlers = new List();
+
+ foreach (var builder in _options.Schemes)
+ {
+ var scheme = builder.Build();
+ AddScheme(scheme);
+ }
+ }
+
+ private readonly AuthenticationOptions _options;
+ private readonly object _lock = new object();
+
+ private readonly IDictionary _schemes;
+ private readonly List _requestHandlers;
+
+ private Task GetDefaultSchemeAsync()
+ => _options.DefaultScheme != null
+ ? GetSchemeAsync(_options.DefaultScheme)
+ : Task.FromResult(null);
+
+ ///
+ /// Returns the scheme that will be used by default for .
+ /// This is typically specified via .
+ /// Otherwise, this will fallback to .
+ ///
+ /// The scheme that will be used by default for .
+ public virtual Task GetDefaultAuthenticateSchemeAsync()
+ => _options.DefaultAuthenticateScheme != null
+ ? GetSchemeAsync(_options.DefaultAuthenticateScheme)
+ : GetDefaultSchemeAsync();
+
+ ///
+ /// Returns the scheme that will be used by default for .
+ /// This is typically specified via .
+ /// Otherwise, this will fallback to .
+ ///
+ /// The scheme that will be used by default for .
+ public virtual Task GetDefaultChallengeSchemeAsync()
+ => _options.DefaultChallengeScheme != null
+ ? GetSchemeAsync(_options.DefaultChallengeScheme)
+ : GetDefaultSchemeAsync();
+
+ ///
+ /// Returns the scheme that will be used by default for .
+ /// This is typically specified via .
+ /// Otherwise, this will fallback to .
+ ///
+ /// The scheme that will be used by default for .
+ public virtual Task GetDefaultForbidSchemeAsync()
+ => _options.DefaultForbidScheme != null
+ ? GetSchemeAsync(_options.DefaultForbidScheme)
+ : GetDefaultChallengeSchemeAsync();
+
+ ///
+ /// Returns the scheme that will be used by default for .
+ /// This is typically specified via .
+ /// Otherwise, this will fallback to .
+ ///
+ /// The scheme that will be used by default for .
+ public virtual Task GetDefaultSignInSchemeAsync()
+ => _options.DefaultSignInScheme != null
+ ? GetSchemeAsync(_options.DefaultSignInScheme)
+ : GetDefaultSchemeAsync();
+
+ ///
+ /// Returns the scheme that will be used by default for .
+ /// This is typically specified via .
+ /// Otherwise this will fallback to if that supoorts sign out.
+ ///
+ /// The scheme that will be used by default for .
+ public virtual Task GetDefaultSignOutSchemeAsync()
+ => _options.DefaultSignOutScheme != null
+ ? GetSchemeAsync(_options.DefaultSignOutScheme)
+ : GetDefaultSignInSchemeAsync();
+
+ ///
+ /// Returns the matching the name, or null.
+ ///
+ /// The name of the authenticationScheme.
+ /// The scheme or null if not found.
+ public virtual Task GetSchemeAsync(string name)
+ => Task.FromResult(_schemes.ContainsKey(name) ? _schemes[name] : null);
+
+ ///
+ /// Returns the schemes in priority order for request handling.
+ ///
+ /// The schemes in priority order for request handling
+ public virtual Task> GetRequestHandlerSchemesAsync()
+ => Task.FromResult>(_requestHandlers);
+
+ ///
+ /// Registers a scheme for use by .
+ ///
+ /// The scheme.
+ public virtual void AddScheme(AuthenticationScheme scheme)
+ {
+ if (_schemes.ContainsKey(scheme.Name))
+ {
+ throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
+ }
+ lock (_lock)
+ {
+ if (_schemes.ContainsKey(scheme.Name))
+ {
+ throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
+ }
+ if (typeof(IAuthenticationRequestHandler).IsAssignableFrom(scheme.HandlerType))
+ {
+ _requestHandlers.Add(scheme);
+ }
+ _schemes[scheme.Name] = scheme;
+ }
+ }
+
+ ///
+ /// Removes a scheme, preventing it from being used by .
+ ///
+ /// The name of the authenticationScheme being removed.
+ public virtual void RemoveScheme(string name)
+ {
+ if (!_schemes.ContainsKey(name))
+ {
+ return;
+ }
+ lock (_lock)
+ {
+ if (_schemes.ContainsKey(name))
+ {
+ var scheme = _schemes[name];
+ _requestHandlers.Remove(scheme);
+ _schemes.Remove(name);
+ }
+ }
+ }
+
+ public virtual Task> GetAllSchemesAsync()
+ => Task.FromResult>(_schemes.Values);
+ }
+}
\ No newline at end of file
diff --git a/src/Http/Authentication.Core/src/AuthenticationService.cs b/src/Http/Authentication.Core/src/AuthenticationService.cs
new file mode 100644
index 0000000000..3e46df2f24
--- /dev/null
+++ b/src/Http/Authentication.Core/src/AuthenticationService.cs
@@ -0,0 +1,303 @@
+// 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.Linq;
+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 .
+ 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 await CreateMissingHandlerException(scheme);
+ }
+
+ var result = await handler.AuthenticateAsync();
+ if (result != null && 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 .
+ /// A task.
+ public virtual async Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties)
+ {
+ 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 await CreateMissingHandlerException(scheme);
+ }
+
+ await handler.ChallengeAsync(properties);
+ }
+
+ ///
+ /// Forbid the specified authentication scheme.
+ ///
+ /// The .
+ /// The name of the authentication scheme.
+ /// The .
+ /// A task.
+ public virtual async Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties)
+ {
+ if (scheme == null)
+ {
+ var defaultForbidScheme = await Schemes.GetDefaultForbidSchemeAsync();
+ scheme = defaultForbidScheme?.Name;
+ if (scheme == null)
+ {
+ throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultForbidScheme found.");
+ }
+ }
+
+ var handler = await Handlers.GetHandlerAsync(context, scheme);
+ if (handler == null)
+ {
+ throw await CreateMissingHandlerException(scheme);
+ }
+
+ await handler.ForbidAsync(properties);
+ }
+
+ ///
+ /// 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 await CreateMissingSignInHandlerException(scheme);
+ }
+
+ var signInHandler = handler as IAuthenticationSignInHandler;
+ if (signInHandler == null)
+ {
+ throw await CreateMismatchedSignInHandlerException(scheme, handler);
+ }
+
+ await signInHandler.SignInAsync(principal, properties);
+ }
+
+ ///
+ /// 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 (scheme == null)
+ {
+ var defaultScheme = await Schemes.GetDefaultSignOutSchemeAsync();
+ scheme = defaultScheme?.Name;
+ if (scheme == null)
+ {
+ throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultSignOutScheme found.");
+ }
+ }
+
+ var handler = await Handlers.GetHandlerAsync(context, scheme);
+ if (handler == null)
+ {
+ throw await CreateMissingSignOutHandlerException(scheme);
+ }
+
+ var signOutHandler = handler as IAuthenticationSignOutHandler;
+ if (signOutHandler == null)
+ {
+ throw await CreateMismatchedSignOutHandlerException(scheme, handler);
+ }
+
+ await signOutHandler.SignOutAsync(properties);
+ }
+
+ private async Task CreateMissingHandlerException(string scheme)
+ {
+ var schemes = string.Join(", ", (await Schemes.GetAllSchemesAsync()).Select(sch => sch.Name));
+
+ var footer = $" Did you forget to call AddAuthentication().Add[SomeAuthHandler](\"{scheme}\",...)?";
+
+ if (string.IsNullOrEmpty(schemes))
+ {
+ return new InvalidOperationException(
+ $"No authentication handlers are registered." + footer);
+ }
+
+ return new InvalidOperationException(
+ $"No authentication handler is registered for the scheme '{scheme}'. The registered schemes are: {schemes}." + footer);
+ }
+
+ private async Task GetAllSignInSchemeNames()
+ {
+ return string.Join(", ", (await Schemes.GetAllSchemesAsync())
+ .Where(sch => typeof(IAuthenticationSignInHandler).IsAssignableFrom(sch.HandlerType))
+ .Select(sch => sch.Name));
+ }
+
+ private async Task CreateMissingSignInHandlerException(string scheme)
+ {
+ var schemes = await GetAllSignInSchemeNames();
+
+ // CookieAuth is the only implementation of sign-in.
+ var footer = $" Did you forget to call AddAuthentication().AddCookies(\"{scheme}\",...)?";
+
+ if (string.IsNullOrEmpty(schemes))
+ {
+ return new InvalidOperationException(
+ $"No sign-in authentication handlers are registered." + footer);
+ }
+
+ return new InvalidOperationException(
+ $"No sign-in authentication handler is registered for the scheme '{scheme}'. The registered sign-in schemes are: {schemes}." + footer);
+ }
+
+ private async Task CreateMismatchedSignInHandlerException(string scheme, IAuthenticationHandler handler)
+ {
+ var schemes = await GetAllSignInSchemeNames();
+
+ var mismatchError = $"The authentication handler registered for scheme '{scheme}' is '{handler.GetType().Name}' which cannot be used for SignInAsync. ";
+
+ if (string.IsNullOrEmpty(schemes))
+ {
+ // CookieAuth is the only implementation of sign-in.
+ return new InvalidOperationException(mismatchError
+ + $"Did you forget to call AddAuthentication().AddCookies(\"Cookies\") and SignInAsync(\"Cookies\",...)?");
+ }
+
+ return new InvalidOperationException(mismatchError + $"The registered sign-in schemes are: {schemes}.");
+ }
+
+ private async Task GetAllSignOutSchemeNames()
+ {
+ return string.Join(", ", (await Schemes.GetAllSchemesAsync())
+ .Where(sch => typeof(IAuthenticationSignOutHandler).IsAssignableFrom(sch.HandlerType))
+ .Select(sch => sch.Name));
+ }
+
+ private async Task CreateMissingSignOutHandlerException(string scheme)
+ {
+ var schemes = await GetAllSignOutSchemeNames();
+
+ var footer = $" Did you forget to call AddAuthentication().AddCookies(\"{scheme}\",...)?";
+
+ if (string.IsNullOrEmpty(schemes))
+ {
+ // CookieAuth is the most common implementation of sign-out, but OpenIdConnect and WsFederation also support it.
+ return new InvalidOperationException($"No sign-out authentication handlers are registered." + footer);
+ }
+
+ return new InvalidOperationException(
+ $"No sign-out authentication handler is registered for the scheme '{scheme}'. The registered sign-out schemes are: {schemes}." + footer);
+ }
+
+ private async Task CreateMismatchedSignOutHandlerException(string scheme, IAuthenticationHandler handler)
+ {
+ var schemes = await GetAllSignOutSchemeNames();
+
+ var mismatchError = $"The authentication handler registered for scheme '{scheme}' is '{handler.GetType().Name}' which cannot be used for {nameof(SignOutAsync)}. ";
+
+ if (string.IsNullOrEmpty(schemes))
+ {
+ // CookieAuth is the most common implementation of sign-out, but OpenIdConnect and WsFederation also support it.
+ return new InvalidOperationException(mismatchError
+ + $"Did you forget to call AddAuthentication().AddCookies(\"Cookies\") and {nameof(SignOutAsync)}(\"Cookies\",...)?");
+ }
+
+ return new InvalidOperationException(mismatchError + $"The registered sign-out schemes are: {schemes}.");
+ }
+ }
+}
diff --git a/src/Http/Authentication.Core/src/Microsoft.AspNetCore.Authentication.Core.csproj b/src/Http/Authentication.Core/src/Microsoft.AspNetCore.Authentication.Core.csproj
new file mode 100644
index 0000000000..c10bfb3656
--- /dev/null
+++ b/src/Http/Authentication.Core/src/Microsoft.AspNetCore.Authentication.Core.csproj
@@ -0,0 +1,18 @@
+
+
+
+ ASP.NET Core common types used by the various authentication middleware components.
+ netstandard2.0
+ $(NoWarn);CS1591
+ true
+ aspnetcore;authentication;security
+ false
+
+
+
+
+
+
+
+
+
diff --git a/src/Http/Authentication.Core/src/NoopClaimsTransformation.cs b/src/Http/Authentication.Core/src/NoopClaimsTransformation.cs
new file mode 100644
index 0000000000..83c488fe42
--- /dev/null
+++ b/src/Http/Authentication.Core/src/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/src/Http/Authentication.Core/src/baseline.netcore.json b/src/Http/Authentication.Core/src/baseline.netcore.json
new file mode 100644
index 0000000000..62aeb44738
--- /dev/null
+++ b/src/Http/Authentication.Core/src/baseline.netcore.json
@@ -0,0 +1,515 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.Authentication.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.AuthenticationFeature",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Authentication.IAuthenticationFeature"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_OriginalPathBase",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationFeature",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_OriginalPathBase",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Microsoft.AspNetCore.Http.PathString"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationFeature",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_OriginalPath",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationFeature",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_OriginalPath",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "Microsoft.AspNetCore.Http.PathString"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationFeature",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.AuthenticationHandlerProvider",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Authentication.IAuthenticationHandlerProvider"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Schemes",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetHandlerAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "authenticationScheme",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationHandlerProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "schemes",
+ "Type": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.AuthenticationSchemeProvider",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "GetDefaultAuthenticateSchemeAsync",
+ "Parameters": [],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetDefaultChallengeSchemeAsync",
+ "Parameters": [],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetDefaultForbidSchemeAsync",
+ "Parameters": [],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetDefaultSignInSchemeAsync",
+ "Parameters": [],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetDefaultSignOutSchemeAsync",
+ "Parameters": [],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetSchemeAsync",
+ "Parameters": [
+ {
+ "Name": "name",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetRequestHandlerSchemesAsync",
+ "Parameters": [],
+ "ReturnType": "System.Threading.Tasks.Task>",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AddScheme",
+ "Parameters": [
+ {
+ "Name": "scheme",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationScheme"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "RemoveScheme",
+ "Parameters": [
+ {
+ "Name": "name",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetAllSchemesAsync",
+ "Parameters": [],
+ "ReturnType": "System.Threading.Tasks.Task>",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "options",
+ "Type": "Microsoft.Extensions.Options.IOptions"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.AuthenticationService",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Authentication.IAuthenticationService"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Schemes",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Handlers",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Authentication.IAuthenticationHandlerProvider",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Transform",
+ "Parameters": [],
+ "ReturnType": "Microsoft.AspNetCore.Authentication.IClaimsTransformation",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AuthenticateAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationService",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ChallengeAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationService",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ForbidAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationService",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "SignInAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ },
+ {
+ "Name": "principal",
+ "Type": "System.Security.Claims.ClaimsPrincipal"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationService",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "SignOutAsync",
+ "Parameters": [
+ {
+ "Name": "context",
+ "Type": "Microsoft.AspNetCore.Http.HttpContext"
+ },
+ {
+ "Name": "scheme",
+ "Type": "System.String"
+ },
+ {
+ "Name": "properties",
+ "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationService",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "schemes",
+ "Type": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider"
+ },
+ {
+ "Name": "handlers",
+ "Type": "Microsoft.AspNetCore.Authentication.IAuthenticationHandlerProvider"
+ },
+ {
+ "Name": "transform",
+ "Type": "Microsoft.AspNetCore.Authentication.IClaimsTransformation"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Authentication.NoopClaimsTransformation",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.Authentication.IClaimsTransformation"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "TransformAsync",
+ "Parameters": [
+ {
+ "Name": "principal",
+ "Type": "System.Security.Claims.ClaimsPrincipal"
+ }
+ ],
+ "ReturnType": "System.Threading.Tasks.Task",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IClaimsTransformation",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.Extensions.DependencyInjection.AuthenticationCoreServiceCollectionExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "AddAuthenticationCore",
+ "Parameters": [
+ {
+ "Name": "services",
+ "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
+ }
+ ],
+ "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "AddAuthenticationCore",
+ "Parameters": [
+ {
+ "Name": "services",
+ "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
+ },
+ {
+ "Name": "configureOptions",
+ "Type": "System.Action"
+ }
+ ],
+ "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Http/Authentication.Core/test/AuthenticationPropertiesTests.cs b/src/Http/Authentication.Core/test/AuthenticationPropertiesTests.cs
new file mode 100644
index 0000000000..639c9b558e
--- /dev/null
+++ b/src/Http/Authentication.Core/test/AuthenticationPropertiesTests.cs
@@ -0,0 +1,207 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Authentication.Core.Test
+{
+ public class AuthenticationPropertiesTests
+ {
+ [Fact]
+ public void DefaultConstructor_EmptyCollections()
+ {
+ var props = new AuthenticationProperties();
+ Assert.Empty(props.Items);
+ Assert.Empty(props.Parameters);
+ }
+
+ [Fact]
+ public void ItemsConstructor_ReusesItemsDictionary()
+ {
+ var items = new Dictionary
+ {
+ ["foo"] = "bar",
+ };
+ var props = new AuthenticationProperties(items);
+ Assert.Same(items, props.Items);
+ Assert.Empty(props.Parameters);
+ }
+
+ [Fact]
+ public void FullConstructor_ReusesDictionaries()
+ {
+ var items = new Dictionary
+ {
+ ["foo"] = "bar",
+ };
+ var parameters = new Dictionary
+ {
+ ["number"] = 1234,
+ ["list"] = new List { "a", "b", "c" },
+ };
+ var props = new AuthenticationProperties(items, parameters);
+ Assert.Same(items, props.Items);
+ Assert.Same(parameters, props.Parameters);
+ }
+
+ [Fact]
+ public void GetSetString()
+ {
+ var props = new AuthenticationProperties();
+ Assert.Null(props.GetString("foo"));
+ Assert.Equal(0, props.Items.Count);
+
+ props.SetString("foo", "foo bar");
+ Assert.Equal("foo bar", props.GetString("foo"));
+ Assert.Equal("foo bar", props.Items["foo"]);
+ Assert.Equal(1, props.Items.Count);
+
+ props.SetString("foo", "foo baz");
+ Assert.Equal("foo baz", props.GetString("foo"));
+ Assert.Equal("foo baz", props.Items["foo"]);
+ Assert.Equal(1, props.Items.Count);
+
+ props.SetString("bar", "xy");
+ Assert.Equal("xy", props.GetString("bar"));
+ Assert.Equal("xy", props.Items["bar"]);
+ Assert.Equal(2, props.Items.Count);
+
+ props.SetString("bar", string.Empty);
+ Assert.Equal(string.Empty, props.GetString("bar"));
+ Assert.Equal(string.Empty, props.Items["bar"]);
+
+ props.SetString("foo", null);
+ Assert.Null(props.GetString("foo"));
+ Assert.Equal(1, props.Items.Count);
+ }
+
+ [Fact]
+ public void GetSetParameter_String()
+ {
+ var props = new AuthenticationProperties();
+ Assert.Null(props.GetParameter("foo"));
+ Assert.Equal(0, props.Parameters.Count);
+
+ props.SetParameter("foo", "foo bar");
+ Assert.Equal("foo bar", props.GetParameter("foo"));
+ Assert.Equal("foo bar", props.Parameters["foo"]);
+ Assert.Equal(1, props.Parameters.Count);
+
+ props.SetParameter("foo", null);
+ Assert.Null(props.GetParameter("foo"));
+ Assert.Null(props.Parameters["foo"]);
+ Assert.Equal(1, props.Parameters.Count);
+ }
+
+ [Fact]
+ public void GetSetParameter_Int()
+ {
+ var props = new AuthenticationProperties();
+ Assert.Null(props.GetParameter("foo"));
+ Assert.Equal(0, props.Parameters.Count);
+
+ props.SetParameter("foo", 123);
+ Assert.Equal(123, props.GetParameter("foo"));
+ Assert.Equal(123, props.Parameters["foo"]);
+ Assert.Equal(1, props.Parameters.Count);
+
+ props.SetParameter("foo", null);
+ Assert.Null(props.GetParameter("foo"));
+ Assert.Null(props.Parameters["foo"]);
+ Assert.Equal(1, props.Parameters.Count);
+ }
+
+ [Fact]
+ public void GetSetParameter_Collection()
+ {
+ var props = new AuthenticationProperties();
+ Assert.Null(props.GetParameter("foo"));
+ Assert.Equal(0, props.Parameters.Count);
+
+ var list = new string[] { "a", "b", "c" };
+ props.SetParameter>("foo", list);
+ Assert.Equal(new string[] { "a", "b", "c" }, props.GetParameter>("foo"));
+ Assert.Same(list, props.Parameters["foo"]);
+ Assert.Equal(1, props.Parameters.Count);
+
+ props.SetParameter>("foo", null);
+ Assert.Null(props.GetParameter>("foo"));
+ Assert.Null(props.Parameters["foo"]);
+ Assert.Equal(1, props.Parameters.Count);
+ }
+
+ [Fact]
+ public void IsPersistent_Test()
+ {
+ var props = new AuthenticationProperties();
+ Assert.False(props.IsPersistent);
+
+ props.IsPersistent = true;
+ Assert.True(props.IsPersistent);
+ Assert.Equal(string.Empty, props.Items.First().Value);
+
+ props.Items.Clear();
+ Assert.False(props.IsPersistent);
+ }
+
+ [Fact]
+ public void RedirectUri_Test()
+ {
+ var props = new AuthenticationProperties();
+ Assert.Null(props.RedirectUri);
+
+ props.RedirectUri = "http://example.com";
+ Assert.Equal("http://example.com", props.RedirectUri);
+ Assert.Equal("http://example.com", props.Items.First().Value);
+
+ props.Items.Clear();
+ Assert.Null(props.RedirectUri);
+ }
+
+ [Fact]
+ public void IssuedUtc_Test()
+ {
+ var props = new AuthenticationProperties();
+ Assert.Null(props.IssuedUtc);
+
+ props.IssuedUtc = new DateTimeOffset(new DateTime(2018, 03, 21, 0, 0, 0, DateTimeKind.Utc));
+ Assert.Equal(new DateTimeOffset(new DateTime(2018, 03, 21, 0, 0, 0, DateTimeKind.Utc)), props.IssuedUtc);
+ Assert.Equal("Wed, 21 Mar 2018 00:00:00 GMT", props.Items.First().Value);
+
+ props.Items.Clear();
+ Assert.Null(props.IssuedUtc);
+ }
+
+ [Fact]
+ public void ExpiresUtc_Test()
+ {
+ var props = new AuthenticationProperties();
+ Assert.Null(props.ExpiresUtc);
+
+ props.ExpiresUtc = new DateTimeOffset(new DateTime(2018, 03, 19, 12, 34, 56, DateTimeKind.Utc));
+ Assert.Equal(new DateTimeOffset(new DateTime(2018, 03, 19, 12, 34, 56, DateTimeKind.Utc)), props.ExpiresUtc);
+ Assert.Equal("Mon, 19 Mar 2018 12:34:56 GMT", props.Items.First().Value);
+
+ props.Items.Clear();
+ Assert.Null(props.ExpiresUtc);
+ }
+
+ [Fact]
+ public void AllowRefresh_Test()
+ {
+ var props = new AuthenticationProperties();
+ Assert.Null(props.AllowRefresh);
+
+ props.AllowRefresh = true;
+ Assert.True(props.AllowRefresh);
+ Assert.Equal("True", props.Items.First().Value);
+
+ props.AllowRefresh = false;
+ Assert.False(props.AllowRefresh);
+ Assert.Equal("False", props.Items.First().Value);
+
+ props.Items.Clear();
+ Assert.Null(props.AllowRefresh);
+ }
+ }
+}
diff --git a/src/Http/Authentication.Core/test/AuthenticationSchemeProviderTests.cs b/src/Http/Authentication.Core/test/AuthenticationSchemeProviderTests.cs
new file mode 100644
index 0000000000..82602000aa
--- /dev/null
+++ b/src/Http/Authentication.Core/test/AuthenticationSchemeProviderTests.cs
@@ -0,0 +1,207 @@
+
+// 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.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ public class AuthenticationSchemeProviderTests
+ {
+ [Fact]
+ public async Task NoDefaultsByDefault()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme("B", "whatever");
+ }).BuildServiceProvider();
+
+ var provider = services.GetRequiredService();
+ Assert.Null(await provider.GetDefaultForbidSchemeAsync());
+ Assert.Null(await provider.GetDefaultAuthenticateSchemeAsync());
+ Assert.Null(await provider.GetDefaultChallengeSchemeAsync());
+ Assert.Null(await provider.GetDefaultSignInSchemeAsync());
+ Assert.Null(await provider.GetDefaultSignOutSchemeAsync());
+ }
+
+ [Fact]
+ public async Task DefaultSchemesFallbackToDefaultScheme()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.DefaultScheme = "B";
+ o.AddScheme("B", "whatever");
+ }).BuildServiceProvider();
+
+ var provider = services.GetRequiredService();
+ Assert.Equal("B", (await provider.GetDefaultForbidSchemeAsync()).Name);
+ Assert.Equal("B", (await provider.GetDefaultAuthenticateSchemeAsync()).Name);
+ Assert.Equal("B", (await provider.GetDefaultChallengeSchemeAsync()).Name);
+ Assert.Equal("B", (await provider.GetDefaultSignInSchemeAsync()).Name);
+ Assert.Equal("B", (await provider.GetDefaultSignOutSchemeAsync()).Name);
+ }
+
+
+ [Fact]
+ public async Task DefaultSignOutFallsbackToSignIn()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme("signin", "whatever");
+ o.AddScheme("foobly", "whatever");
+ o.DefaultSignInScheme = "signin";
+ }).BuildServiceProvider();
+
+ var provider = services.GetRequiredService();
+ var scheme = await provider.GetDefaultSignOutSchemeAsync();
+ Assert.NotNull(scheme);
+ Assert.Equal("signin", scheme.Name);
+ }
+
+ [Fact]
+ public async Task DefaultForbidFallsbackToChallenge()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme("challenge", "whatever");
+ o.AddScheme("foobly", "whatever");
+ o.DefaultChallengeScheme = "challenge";
+ }).BuildServiceProvider();
+
+ var provider = services.GetRequiredService();
+ var scheme = await provider.GetDefaultForbidSchemeAsync();
+ Assert.NotNull(scheme);
+ Assert.Equal("challenge", scheme.Name);
+ }
+
+ [Fact]
+ public async Task DefaultSchemesAreSet()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme("A", "whatever");
+ o.AddScheme("B", "whatever");
+ o.AddScheme("C", "whatever");
+ o.AddScheme("Def", "whatever");
+ o.DefaultScheme = "Def";
+ o.DefaultChallengeScheme = "A";
+ o.DefaultForbidScheme = "B";
+ o.DefaultSignInScheme = "C";
+ o.DefaultSignOutScheme = "A";
+ o.DefaultAuthenticateScheme = "C";
+ }).BuildServiceProvider();
+
+ var provider = services.GetRequiredService();
+ Assert.Equal("B", (await provider.GetDefaultForbidSchemeAsync()).Name);
+ Assert.Equal("C", (await provider.GetDefaultAuthenticateSchemeAsync()).Name);
+ Assert.Equal("A", (await provider.GetDefaultChallengeSchemeAsync()).Name);
+ Assert.Equal("C", (await provider.GetDefaultSignInSchemeAsync()).Name);
+ Assert.Equal("A", (await provider.GetDefaultSignOutSchemeAsync()).Name);
+ }
+
+ [Fact]
+ public async Task SignOutWillDefaultsToSignInThatDoesNotSignOut()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme("signin", "whatever");
+ o.DefaultSignInScheme = "signin";
+ }).BuildServiceProvider();
+
+ var provider = services.GetRequiredService();
+ Assert.NotNull(await provider.GetDefaultSignOutSchemeAsync());
+ }
+
+ [Fact]
+ public void SchemeRegistrationIsCaseSensitive()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme("signin", "whatever");
+ o.AddScheme("signin", "whatever");
+ }).BuildServiceProvider();
+
+ var error = Assert.Throws(() => services.GetRequiredService());
+
+ Assert.Contains("Scheme already exists: signin", error.Message);
+ }
+
+ [Fact]
+ public async Task LookupUsesProvidedStringComparer()
+ {
+ var services = new ServiceCollection().AddOptions()
+ .AddSingleton()
+ .AddAuthenticationCore(o => o.AddScheme("signin", "whatever"))
+ .BuildServiceProvider();
+
+ var provider = services.GetRequiredService();
+
+ var a = await provider.GetSchemeAsync("signin");
+ var b = await provider.GetSchemeAsync("SignIn");
+ var c = await provider.GetSchemeAsync("SIGNIN");
+
+ Assert.NotNull(a);
+ Assert.Same(a, b);
+ Assert.Same(b, c);
+ }
+
+ private class Handler : IAuthenticationHandler
+ {
+ public Task AuthenticateAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ChallengeAsync(AuthenticationProperties properties)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ForbidAsync(AuthenticationProperties properties)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class SignInHandler : Handler, IAuthenticationSignInHandler
+ {
+ public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SignOutAsync(AuthenticationProperties properties)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class SignOutHandler : Handler, IAuthenticationSignOutHandler
+ {
+ public Task SignOutAsync(AuthenticationProperties properties)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class IgnoreCaseSchemeProvider : AuthenticationSchemeProvider
+ {
+ public IgnoreCaseSchemeProvider(IOptions options)
+ : base(options, new Dictionary(StringComparer.OrdinalIgnoreCase))
+ {
+ }
+ }
+ }
+}
diff --git a/src/Http/Authentication.Core/test/AuthenticationServiceTests.cs b/src/Http/Authentication.Core/test/AuthenticationServiceTests.cs
new file mode 100644
index 0000000000..e21ea40d51
--- /dev/null
+++ b/src/Http/Authentication.Core/test/AuthenticationServiceTests.cs
@@ -0,0 +1,355 @@
+// 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;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+ public class AuthenticationServiceTests
+ {
+ [Fact]
+ public async Task AuthenticateThrowsForSchemeMismatch()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme("base", "whatever");
+ }).BuildServiceProvider();
+ var context = new DefaultHttpContext();
+ context.RequestServices = services;
+
+ await context.AuthenticateAsync("base");
+ var ex = await Assert.ThrowsAsync(() => context.AuthenticateAsync("missing"));
+ Assert.Contains("base", ex.Message);
+ }
+
+ [Fact]
+ public async Task ChallengeThrowsForSchemeMismatch()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme("base", "whatever");
+ }).BuildServiceProvider();
+ var context = new DefaultHttpContext();
+ context.RequestServices = services;
+
+ await context.ChallengeAsync("base");
+ var ex = await Assert.ThrowsAsync(() => context.ChallengeAsync("missing"));
+ Assert.Contains("base", ex.Message);
+ }
+
+ [Fact]
+ public async Task ForbidThrowsForSchemeMismatch()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme("base", "whatever");
+ }).BuildServiceProvider();
+ var context = new DefaultHttpContext();
+ context.RequestServices = services;
+
+ await context.ForbidAsync("base");
+ var ex = await Assert.ThrowsAsync(() => context.ForbidAsync("missing"));
+ Assert.Contains("base", ex.Message);
+ }
+
+ [Fact]
+ public async Task CanOnlySignInIfSupported()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme("uber", "whatever");
+ o.AddScheme("base", "whatever");
+ o.AddScheme("signin", "whatever");
+ o.AddScheme("signout", "whatever");
+ }).BuildServiceProvider();
+ var context = new DefaultHttpContext();
+ context.RequestServices = services;
+
+ await context.SignInAsync("uber", new ClaimsPrincipal(), null);
+ var ex = await Assert.ThrowsAsync(() => context.SignInAsync("base", new ClaimsPrincipal(), null));
+ Assert.Contains("uber", ex.Message);
+ Assert.Contains("signin", ex.Message);
+ await context.SignInAsync("signin", new ClaimsPrincipal(), null);
+ ex = await Assert.ThrowsAsync(() => context.SignInAsync("signout", new ClaimsPrincipal(), null));
+ Assert.Contains("uber", ex.Message);
+ Assert.Contains("signin", ex.Message);
+ }
+
+ [Fact]
+ public async Task CanOnlySignOutIfSupported()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme("uber", "whatever");
+ o.AddScheme("base", "whatever");
+ o.AddScheme("signin", "whatever");
+ o.AddScheme("signout", "whatever");
+ }).BuildServiceProvider();
+ var context = new DefaultHttpContext();
+ context.RequestServices = services;
+
+ await context.SignOutAsync("uber");
+ var ex = await Assert.ThrowsAsync(() => context.SignOutAsync("base"));
+ Assert.Contains("uber", ex.Message);
+ Assert.Contains("signout", ex.Message);
+ await context.SignOutAsync("signout");
+ await context.SignOutAsync("signin");
+ }
+
+ [Fact]
+ public async Task ServicesWithDefaultIAuthenticationHandlerMethodsTest()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme("base", "whatever");
+ o.DefaultScheme = "base";
+ }).BuildServiceProvider();
+ var context = new DefaultHttpContext();
+ context.RequestServices = services;
+
+ await context.AuthenticateAsync();
+ await context.ChallengeAsync();
+ await context.ForbidAsync();
+ var ex = await Assert.ThrowsAsync(() => context.SignOutAsync());
+ Assert.Contains("cannot be used for SignOutAsync", ex.Message);
+ ex = await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal()));
+ Assert.Contains("cannot be used for SignInAsync", ex.Message);
+ }
+
+ [Fact]
+ public async Task ServicesWithDefaultUberMethodsTest()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme("base", "whatever");
+ o.DefaultScheme = "base";
+ }).BuildServiceProvider();
+ var context = new DefaultHttpContext();
+ context.RequestServices = services;
+
+ await context.AuthenticateAsync();
+ await context.ChallengeAsync();
+ await context.ForbidAsync();
+ await context.SignOutAsync();
+ await context.SignInAsync(new ClaimsPrincipal());
+ }
+
+ [Fact]
+ public async Task ServicesWithDefaultSignInMethodsTest()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme("base", "whatever");
+ o.DefaultScheme = "base";
+ }).BuildServiceProvider();
+ var context = new DefaultHttpContext();
+ context.RequestServices = services;
+
+ await context.AuthenticateAsync();
+ await context.ChallengeAsync();
+ await context.ForbidAsync();
+ await context.SignOutAsync();
+ await context.SignInAsync(new ClaimsPrincipal());
+ }
+
+ [Fact]
+ public async Task ServicesWithDefaultSignOutMethodsTest()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme("base", "whatever");
+ o.DefaultScheme = "base";
+ }).BuildServiceProvider();
+ var context = new DefaultHttpContext();
+ context.RequestServices = services;
+
+ await context.AuthenticateAsync();
+ await context.ChallengeAsync();
+ await context.ForbidAsync();
+ await context.SignOutAsync();
+ var ex = await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal()));
+ Assert.Contains("cannot be used for SignInAsync", ex.Message);
+ }
+
+ [Fact]
+ public async Task ServicesWithDefaultForbidMethod_CallsForbidMethod()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme("forbid", "whatever");
+ o.DefaultForbidScheme = "forbid";
+ }).BuildServiceProvider();
+ var context = new DefaultHttpContext();
+ context.RequestServices = services;
+
+ await context.ForbidAsync();
+ }
+
+
+ private class BaseHandler : IAuthenticationHandler
+ {
+ public Task AuthenticateAsync()
+ {
+ return Task.FromResult(AuthenticateResult.NoResult());
+ }
+
+ public Task ChallengeAsync(AuthenticationProperties properties)
+ {
+ return Task.FromResult(0);
+ }
+
+ public Task ForbidAsync(AuthenticationProperties properties)
+ {
+ return Task.FromResult(0);
+ }
+
+ public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+ {
+ return Task.FromResult(0);
+ }
+ }
+
+ private class SignInHandler : IAuthenticationSignInHandler
+ {
+ public Task AuthenticateAsync()
+ {
+ return Task.FromResult(AuthenticateResult.NoResult());
+ }
+
+ public Task ChallengeAsync(AuthenticationProperties properties)
+ {
+ return Task.FromResult(0);
+ }
+
+ public Task ForbidAsync(AuthenticationProperties properties)
+ {
+ return Task.FromResult(0);
+ }
+
+ public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+ {
+ return Task.FromResult(0);
+ }
+
+ public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
+ {
+ return Task.FromResult(0);
+ }
+
+ public Task SignOutAsync(AuthenticationProperties properties)
+ {
+ return Task.FromResult(0);
+ }
+ }
+
+ public class SignOutHandler : IAuthenticationSignOutHandler
+ {
+ public Task AuthenticateAsync()
+ {
+ return Task.FromResult(AuthenticateResult.NoResult());
+ }
+
+ public Task ChallengeAsync(AuthenticationProperties properties)
+ {
+ return Task.FromResult(0);
+ }
+
+ public Task ForbidAsync(AuthenticationProperties properties)
+ {
+ return Task.FromResult(0);
+ }
+
+ public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+ {
+ return Task.FromResult(0);
+ }
+
+ public Task SignOutAsync(AuthenticationProperties properties)
+ {
+ return Task.FromResult(0);
+ }
+ }
+
+ private class UberHandler : IAuthenticationHandler, IAuthenticationRequestHandler, IAuthenticationSignInHandler, IAuthenticationSignOutHandler
+ {
+ public Task AuthenticateAsync()
+ {
+ return Task.FromResult(AuthenticateResult.NoResult());
+ }
+
+ public Task ChallengeAsync(AuthenticationProperties properties)
+ {
+ return Task.FromResult(0);
+ }
+
+ public Task ForbidAsync(AuthenticationProperties properties)
+ {
+ return Task.FromResult(0);
+ }
+
+ public Task HandleRequestAsync()
+ {
+ return Task.FromResult(false);
+ }
+
+ public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+ {
+ return Task.FromResult(0);
+ }
+
+ public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
+ {
+ return Task.FromResult(0);
+ }
+
+ public Task SignOutAsync(AuthenticationProperties properties)
+ {
+ return Task.FromResult(0);
+ }
+ }
+
+ private class ForbidHandler : IAuthenticationHandler, IAuthenticationRequestHandler, IAuthenticationSignInHandler, IAuthenticationSignOutHandler
+ {
+ public Task AuthenticateAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ChallengeAsync(AuthenticationProperties properties)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ForbidAsync(AuthenticationProperties properties)
+ {
+ return Task.FromResult(0);
+ }
+
+ public Task HandleRequestAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+ {
+ return Task.FromResult(0);
+ }
+
+ public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SignOutAsync(AuthenticationProperties properties)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ }
+}
diff --git a/src/Http/Authentication.Core/test/Microsoft.AspNetCore.Authentication.Core.Test.csproj b/src/Http/Authentication.Core/test/Microsoft.AspNetCore.Authentication.Core.Test.csproj
new file mode 100644
index 0000000000..4819703197
--- /dev/null
+++ b/src/Http/Authentication.Core/test/Microsoft.AspNetCore.Authentication.Core.Test.csproj
@@ -0,0 +1,12 @@
+
+
+
+ $(StandardTestTfms)
+
+
+
+
+
+
+
+
diff --git a/src/Http/Authentication.Core/test/TokenExtensionTests.cs b/src/Http/Authentication.Core/test/TokenExtensionTests.cs
new file mode 100644
index 0000000000..7215d526e9
--- /dev/null
+++ b/src/Http/Authentication.Core/test/TokenExtensionTests.cs
@@ -0,0 +1,200 @@
+// 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.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+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.Null(props.GetTokenValue("One"));
+ Assert.Null(props.GetTokenValue("Two"));
+ Assert.Null(props.GetTokenValue("Three"));
+ Assert.Single(props.GetTokens());
+ }
+
+ [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());
+ }
+
+ [Fact]
+ public async Task GetTokenWorksWithDefaultAuthenticateScheme()
+ {
+ var context = new DefaultHttpContext();
+ var services = new ServiceCollection().AddOptions()
+ .AddAuthenticationCore(o =>
+ {
+ o.DefaultScheme = "simple";
+ o.AddScheme("simple", s => s.HandlerType = typeof(SimpleAuth));
+ });
+ context.RequestServices = services.BuildServiceProvider();
+
+ Assert.Equal("1", await context.GetTokenAsync("One"));
+ Assert.Equal("2", await context.GetTokenAsync("Two"));
+ Assert.Equal("3", await context.GetTokenAsync("Three"));
+ }
+
+ [Fact]
+ public async Task GetTokenWorksWithExplicitScheme()
+ {
+ var context = new DefaultHttpContext();
+ var services = new ServiceCollection().AddOptions()
+ .AddAuthenticationCore(o => o.AddScheme("simple", s => s.HandlerType = typeof(SimpleAuth)));
+ context.RequestServices = services.BuildServiceProvider();
+
+ Assert.Equal("1", await context.GetTokenAsync("simple", "One"));
+ Assert.Equal("2", await context.GetTokenAsync("simple", "Two"));
+ Assert.Equal("3", await context.GetTokenAsync("simple", "Three"));
+ }
+
+ private class SimpleAuth : IAuthenticationHandler
+ {
+ public Task AuthenticateAsync()
+ {
+ 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);
+ return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(), props, "simple")));
+ }
+
+ public Task ChallengeAsync(AuthenticationProperties properties)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ForbidAsync(AuthenticationProperties properties)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+ {
+ return Task.FromResult(0);
+ }
+
+ public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SignOutAsync(AuthenticationProperties properties)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ }
+}
diff --git a/src/Http/Headers/src/BaseHeaderParser.cs b/src/Http/Headers/src/BaseHeaderParser.cs
new file mode 100644
index 0000000000..f3caaafb70
--- /dev/null
+++ b/src/Http/Headers/src/BaseHeaderParser.cs
@@ -0,0 +1,72 @@
+// 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.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+ internal abstract class BaseHeaderParser : HttpHeaderParser
+ {
+ protected BaseHeaderParser(bool supportsMultipleValues)
+ : base(supportsMultipleValues)
+ {
+ }
+
+ protected abstract int GetParsedValueLength(StringSegment value, int startIndex, out T parsedValue);
+
+ public sealed override bool TryParseValue(StringSegment value, ref int index, out T parsedValue)
+ {
+ parsedValue = default(T);
+
+ // If multiple values are supported (i.e. list of values), then accept an empty string: The header may
+ // be added multiple times to the request/response message. E.g.
+ // Accept: text/xml; q=1
+ // Accept:
+ // Accept: text/plain; q=0.2
+ if (StringSegment.IsNullOrEmpty(value) || (index == value.Length))
+ {
+ return SupportsMultipleValues;
+ }
+
+ var separatorFound = false;
+ var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, index, SupportsMultipleValues,
+ out separatorFound);
+
+ if (separatorFound && !SupportsMultipleValues)
+ {
+ return false; // leading separators not allowed if we don't support multiple values.
+ }
+
+ if (current == value.Length)
+ {
+ if (SupportsMultipleValues)
+ {
+ index = current;
+ }
+ return SupportsMultipleValues;
+ }
+
+ T result;
+ var length = GetParsedValueLength(value, current, out result);
+
+ if (length == 0)
+ {
+ return false;
+ }
+
+ current = current + length;
+ current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, SupportsMultipleValues,
+ out separatorFound);
+
+ // If we support multiple values and we've not reached the end of the string, then we must have a separator.
+ if ((separatorFound && !SupportsMultipleValues) || (!separatorFound && (current < value.Length)))
+ {
+ return false;
+ }
+
+ index = current;
+ parsedValue = result;
+ return true;
+ }
+ }
+}
diff --git a/src/Http/Headers/src/CacheControlHeaderValue.cs b/src/Http/Headers/src/CacheControlHeaderValue.cs
new file mode 100644
index 0000000000..81e18faf47
--- /dev/null
+++ b/src/Http/Headers/src/CacheControlHeaderValue.cs
@@ -0,0 +1,664 @@
+// 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.Diagnostics.Contracts;
+using System.Globalization;
+using System.Text;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+ public class CacheControlHeaderValue
+ {
+ public static readonly string PublicString = "public";
+ public static readonly string PrivateString = "private";
+ public static readonly string MaxAgeString = "max-age";
+ public static readonly string SharedMaxAgeString = "s-maxage";
+ public static readonly string NoCacheString = "no-cache";
+ public static readonly string NoStoreString = "no-store";
+ public static readonly string MaxStaleString = "max-stale";
+ public static readonly string MinFreshString = "min-fresh";
+ public static readonly string NoTransformString = "no-transform";
+ public static readonly string OnlyIfCachedString = "only-if-cached";
+ public static readonly string MustRevalidateString = "must-revalidate";
+ public static readonly string ProxyRevalidateString = "proxy-revalidate";
+
+ // The Cache-Control header is special: It is a header supporting a list of values, but we represent the list
+ // as _one_ instance of CacheControlHeaderValue. I.e we set 'SupportsMultipleValues' to 'true' since it is
+ // OK to have multiple Cache-Control headers in a request/response message. However, after parsing all
+ // Cache-Control headers, only one instance of CacheControlHeaderValue is created (if all headers contain valid
+ // values, otherwise we may have multiple strings containing the invalid values).
+ private static readonly HttpHeaderParser Parser
+ = new GenericHeaderParser(true, GetCacheControlLength);
+
+ private static readonly Action CheckIsValidTokenAction = CheckIsValidToken;
+
+ private bool _noCache;
+ private ICollection _noCacheHeaders;
+ private bool _noStore;
+ private TimeSpan? _maxAge;
+ private TimeSpan? _sharedMaxAge;
+ private bool _maxStale;
+ private TimeSpan? _maxStaleLimit;
+ private TimeSpan? _minFresh;
+ private bool _noTransform;
+ private bool _onlyIfCached;
+ private bool _public;
+ private bool _private;
+ private ICollection _privateHeaders;
+ private bool _mustRevalidate;
+ private bool _proxyRevalidate;
+ private IList _extensions;
+
+ public CacheControlHeaderValue()
+ {
+ // This type is unique in that there is no single required parameter.
+ }
+
+ public bool NoCache
+ {
+ get { return _noCache; }
+ set { _noCache = value; }
+ }
+
+ public ICollection NoCacheHeaders
+ {
+ get
+ {
+ if (_noCacheHeaders == null)
+ {
+ _noCacheHeaders = new ObjectCollection(CheckIsValidTokenAction);
+ }
+ return _noCacheHeaders;
+ }
+ }
+
+ public bool NoStore
+ {
+ get { return _noStore; }
+ set { _noStore = value; }
+ }
+
+ public TimeSpan? MaxAge
+ {
+ get { return _maxAge; }
+ set { _maxAge = value; }
+ }
+
+ public TimeSpan? SharedMaxAge
+ {
+ get { return _sharedMaxAge; }
+ set { _sharedMaxAge = value; }
+ }
+
+ public bool MaxStale
+ {
+ get { return _maxStale; }
+ set { _maxStale = value; }
+ }
+
+ public TimeSpan? MaxStaleLimit
+ {
+ get { return _maxStaleLimit; }
+ set { _maxStaleLimit = value; }
+ }
+
+ public TimeSpan? MinFresh
+ {
+ get { return _minFresh; }
+ set { _minFresh = value; }
+ }
+
+ public bool NoTransform
+ {
+ get { return _noTransform; }
+ set { _noTransform = value; }
+ }
+
+ public bool OnlyIfCached
+ {
+ get { return _onlyIfCached; }
+ set { _onlyIfCached = value; }
+ }
+
+ public bool Public
+ {
+ get { return _public; }
+ set { _public = value; }
+ }
+
+ public bool Private
+ {
+ get { return _private; }
+ set { _private = value; }
+ }
+
+ public ICollection PrivateHeaders
+ {
+ get
+ {
+ if (_privateHeaders == null)
+ {
+ _privateHeaders = new ObjectCollection(CheckIsValidTokenAction);
+ }
+ return _privateHeaders;
+ }
+ }
+
+ public bool MustRevalidate
+ {
+ get { return _mustRevalidate; }
+ set { _mustRevalidate = value; }
+ }
+
+ public bool ProxyRevalidate
+ {
+ get { return _proxyRevalidate; }
+ set { _proxyRevalidate = value; }
+ }
+
+ public IList Extensions
+ {
+ get
+ {
+ if (_extensions == null)
+ {
+ _extensions = new ObjectCollection();
+ }
+ return _extensions;
+ }
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+
+ AppendValueIfRequired(sb, _noStore, NoStoreString);
+ AppendValueIfRequired(sb, _noTransform, NoTransformString);
+ AppendValueIfRequired(sb, _onlyIfCached, OnlyIfCachedString);
+ AppendValueIfRequired(sb, _public, PublicString);
+ AppendValueIfRequired(sb, _mustRevalidate, MustRevalidateString);
+ AppendValueIfRequired(sb, _proxyRevalidate, ProxyRevalidateString);
+
+ if (_noCache)
+ {
+ AppendValueWithSeparatorIfRequired(sb, NoCacheString);
+ if ((_noCacheHeaders != null) && (_noCacheHeaders.Count > 0))
+ {
+ sb.Append("=\"");
+ AppendValues(sb, _noCacheHeaders);
+ sb.Append('\"');
+ }
+ }
+
+ if (_maxAge.HasValue)
+ {
+ AppendValueWithSeparatorIfRequired(sb, MaxAgeString);
+ sb.Append('=');
+ sb.Append(((int)_maxAge.Value.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
+ }
+
+ if (_sharedMaxAge.HasValue)
+ {
+ AppendValueWithSeparatorIfRequired(sb, SharedMaxAgeString);
+ sb.Append('=');
+ sb.Append(((int)_sharedMaxAge.Value.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
+ }
+
+ if (_maxStale)
+ {
+ AppendValueWithSeparatorIfRequired(sb, MaxStaleString);
+ if (_maxStaleLimit.HasValue)
+ {
+ sb.Append('=');
+ sb.Append(((int)_maxStaleLimit.Value.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
+ }
+ }
+
+ if (_minFresh.HasValue)
+ {
+ AppendValueWithSeparatorIfRequired(sb, MinFreshString);
+ sb.Append('=');
+ sb.Append(((int)_minFresh.Value.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
+ }
+
+ if (_private)
+ {
+ AppendValueWithSeparatorIfRequired(sb, PrivateString);
+ if ((_privateHeaders != null) && (_privateHeaders.Count > 0))
+ {
+ sb.Append("=\"");
+ AppendValues(sb, _privateHeaders);
+ sb.Append('\"');
+ }
+ }
+
+ NameValueHeaderValue.ToString(_extensions, ',', false, sb);
+
+ return sb.ToString();
+ }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as CacheControlHeaderValue;
+
+ if (other == null)
+ {
+ return false;
+ }
+
+ if ((_noCache != other._noCache) || (_noStore != other._noStore) || (_maxAge != other._maxAge) ||
+ (_sharedMaxAge != other._sharedMaxAge) || (_maxStale != other._maxStale) ||
+ (_maxStaleLimit != other._maxStaleLimit) || (_minFresh != other._minFresh) ||
+ (_noTransform != other._noTransform) || (_onlyIfCached != other._onlyIfCached) ||
+ (_public != other._public) || (_private != other._private) ||
+ (_mustRevalidate != other._mustRevalidate) || (_proxyRevalidate != other._proxyRevalidate))
+ {
+ return false;
+ }
+
+ if (!HeaderUtilities.AreEqualCollections(_noCacheHeaders, other._noCacheHeaders,
+ StringSegmentComparer.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ if (!HeaderUtilities.AreEqualCollections(_privateHeaders, other._privateHeaders,
+ StringSegmentComparer.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ if (!HeaderUtilities.AreEqualCollections(_extensions, other._extensions))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public override int GetHashCode()
+ {
+ // Use a different bit for bool fields: bool.GetHashCode() will return 0 (false) or 1 (true). So we would
+ // end up having the same hash code for e.g. two instances where one has only noCache set and the other
+ // only noStore.
+ int result = _noCache.GetHashCode() ^ (_noStore.GetHashCode() << 1) ^ (_maxStale.GetHashCode() << 2) ^
+ (_noTransform.GetHashCode() << 3) ^ (_onlyIfCached.GetHashCode() << 4) ^
+ (_public.GetHashCode() << 5) ^ (_private.GetHashCode() << 6) ^
+ (_mustRevalidate.GetHashCode() << 7) ^ (_proxyRevalidate.GetHashCode() << 8);
+
+ // XOR the hashcode of timespan values with different numbers to make sure two instances with the same
+ // timespan set on different fields result in different hashcodes.
+ result = result ^ (_maxAge.HasValue ? _maxAge.Value.GetHashCode() ^ 1 : 0) ^
+ (_sharedMaxAge.HasValue ? _sharedMaxAge.Value.GetHashCode() ^ 2 : 0) ^
+ (_maxStaleLimit.HasValue ? _maxStaleLimit.Value.GetHashCode() ^ 4 : 0) ^
+ (_minFresh.HasValue ? _minFresh.Value.GetHashCode() ^ 8 : 0);
+
+ if ((_noCacheHeaders != null) && (_noCacheHeaders.Count > 0))
+ {
+ foreach (var noCacheHeader in _noCacheHeaders)
+ {
+ result = result ^ StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(noCacheHeader);
+ }
+ }
+
+ if ((_privateHeaders != null) && (_privateHeaders.Count > 0))
+ {
+ foreach (var privateHeader in _privateHeaders)
+ {
+ result = result ^ StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(privateHeader);
+ }
+ }
+
+ if ((_extensions != null) && (_extensions.Count > 0))
+ {
+ foreach (var extension in _extensions)
+ {
+ result = result ^ extension.GetHashCode();
+ }
+ }
+
+ return result;
+ }
+
+ public static CacheControlHeaderValue Parse(StringSegment input)
+ {
+ int index = 0;
+ // Cache-Control is unusual because there are no required values so the parser will succeed for an empty string, but still return null.
+ var result = Parser.ParseValue(input, ref index);
+ if (result == null)
+ {
+ throw new FormatException("No cache directives found.");
+ }
+ return result;
+ }
+
+ public static bool TryParse(StringSegment input, out CacheControlHeaderValue parsedValue)
+ {
+ int index = 0;
+ // Cache-Control is unusual because there are no required values so the parser will succeed for an empty string, but still return null.
+ if (Parser.TryParseValue(input, ref index, out parsedValue) && parsedValue != null)
+ {
+ return true;
+ }
+ parsedValue = null;
+ return false;
+ }
+
+ private static int GetCacheControlLength(StringSegment input, int startIndex, out CacheControlHeaderValue parsedValue)
+ {
+ Contract.Requires(startIndex >= 0);
+
+ parsedValue = null;
+
+ if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+ {
+ return 0;
+ }
+
+ // Cache-Control header consists of a list of name/value pairs, where the value is optional. So use an
+ // instance of NameValueHeaderParser to parse the string.
+ var current = startIndex;
+ NameValueHeaderValue nameValue = null;
+ var nameValueList = new List();
+ while (current < input.Length)
+ {
+ if (!NameValueHeaderValue.MultipleValueParser.TryParseValue(input, ref current, out nameValue))
+ {
+ return 0;
+ }
+
+ nameValueList.Add(nameValue);
+ }
+
+ // If we get here, we were able to successfully parse the string as list of name/value pairs. Now analyze
+ // the name/value pairs.
+
+ // Cache-Control is a header supporting lists of values. However, expose the header as an instance of
+ // CacheControlHeaderValue.
+ var result = new CacheControlHeaderValue();
+
+ if (!TrySetCacheControlValues(result, nameValueList))
+ {
+ return 0;
+ }
+
+ parsedValue = result;
+
+ // If we get here we successfully parsed the whole string.
+ return input.Length - startIndex;
+ }
+
+ private static bool TrySetCacheControlValues(
+ CacheControlHeaderValue cc,
+ List nameValueList)
+ {
+ for (var i = 0; i < nameValueList.Count; i++)
+ {
+ var nameValue = nameValueList[i];
+ var name = nameValue.Name;
+ var success = true;
+
+ switch (name.Length)
+ {
+ case 6:
+ if (StringSegment.Equals(PublicString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = TrySetTokenOnlyValue(nameValue, ref cc._public);
+ }
+ else
+ {
+ goto default;
+ }
+ break;
+
+ case 7:
+ if (StringSegment.Equals(MaxAgeString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = TrySetTimeSpan(nameValue, ref cc._maxAge);
+ }
+ else if(StringSegment.Equals(PrivateString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = TrySetOptionalTokenList(nameValue, ref cc._private, ref cc._privateHeaders);
+ }
+ else
+ {
+ goto default;
+ }
+ break;
+
+ case 8:
+ if (StringSegment.Equals(NoCacheString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = TrySetOptionalTokenList(nameValue, ref cc._noCache, ref cc._noCacheHeaders);
+ }
+ else if (StringSegment.Equals(NoStoreString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = TrySetTokenOnlyValue(nameValue, ref cc._noStore);
+ }
+ else if (StringSegment.Equals(SharedMaxAgeString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = TrySetTimeSpan(nameValue, ref cc._sharedMaxAge);
+ }
+ else
+ {
+ goto default;
+ }
+ break;
+
+ case 9:
+ if (StringSegment.Equals(MaxStaleString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = ((nameValue.Value == null) || TrySetTimeSpan(nameValue, ref cc._maxStaleLimit));
+ if (success)
+ {
+ cc._maxStale = true;
+ }
+ }
+ else if (StringSegment.Equals(MinFreshString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = TrySetTimeSpan(nameValue, ref cc._minFresh);
+ }
+ else
+ {
+ goto default;
+ }
+ break;
+
+ case 12:
+ if (StringSegment.Equals(NoTransformString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = TrySetTokenOnlyValue(nameValue, ref cc._noTransform);
+ }
+ else
+ {
+ goto default;
+ }
+ break;
+
+ case 14:
+ if (StringSegment.Equals(OnlyIfCachedString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = TrySetTokenOnlyValue(nameValue, ref cc._onlyIfCached);
+ }
+ else
+ {
+ goto default;
+ }
+ break;
+
+ case 15:
+ if (StringSegment.Equals(MustRevalidateString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = TrySetTokenOnlyValue(nameValue, ref cc._mustRevalidate);
+ }
+ else
+ {
+ goto default;
+ }
+ break;
+
+ case 16:
+ if (StringSegment.Equals(ProxyRevalidateString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = TrySetTokenOnlyValue(nameValue, ref cc._proxyRevalidate);
+ }
+ else
+ {
+ goto default;
+ }
+ break;
+
+ default:
+ cc.Extensions.Add(nameValue); // success is always true
+ break;
+ }
+
+ if (!success)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static bool TrySetTokenOnlyValue(NameValueHeaderValue nameValue, ref bool boolField)
+ {
+ if (nameValue.Value != null)
+ {
+ return false;
+ }
+
+ boolField = true;
+ return true;
+ }
+
+ private static bool TrySetOptionalTokenList(
+ NameValueHeaderValue nameValue,
+ ref bool boolField,
+ ref ICollection destination)
+ {
+ Contract.Requires(nameValue != null);
+
+ if (nameValue.Value == null)
+ {
+ boolField = true;
+ return true;
+ }
+
+ // We need the string to be at least 3 chars long: 2x quotes and at least 1 character. Also make sure we
+ // have a quoted string. Note that NameValueHeaderValue will never have leading/trailing whitespaces.
+ var valueString = nameValue.Value;
+ if ((valueString.Length < 3) || (valueString[0] != '\"') || (valueString[valueString.Length - 1] != '\"'))
+ {
+ return false;
+ }
+
+ // We have a quoted string. Now verify that the string contains a list of valid tokens separated by ','.
+ var current = 1; // skip the initial '"' character.
+ var maxLength = valueString.Length - 1; // -1 because we don't want to parse the final '"'.
+ var separatorFound = false;
+ var originalValueCount = destination == null ? 0 : destination.Count;
+ while (current < maxLength)
+ {
+ current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(valueString, current, true,
+ out separatorFound);
+
+ if (current == maxLength)
+ {
+ break;
+ }
+
+ var tokenLength = HttpRuleParser.GetTokenLength(valueString, current);
+
+ if (tokenLength == 0)
+ {
+ // We already skipped whitespaces and separators. If we don't have a token it must be an invalid
+ // character.
+ return false;
+ }
+
+ if (destination == null)
+ {
+ destination = new ObjectCollection(CheckIsValidTokenAction);
+ }
+
+ destination.Add(valueString.Subsegment(current, tokenLength));
+
+ current = current + tokenLength;
+ }
+
+ // After parsing a valid token list, we expect to have at least one value
+ if ((destination != null) && (destination.Count > originalValueCount))
+ {
+ boolField = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool TrySetTimeSpan(NameValueHeaderValue nameValue, ref TimeSpan? timeSpan)
+ {
+ Contract.Requires(nameValue != null);
+
+ if (nameValue.Value == null)
+ {
+ return false;
+ }
+
+ int seconds;
+ if (!HeaderUtilities.TryParseNonNegativeInt32(nameValue.Value, out seconds))
+ {
+ return false;
+ }
+
+ timeSpan = new TimeSpan(0, 0, seconds);
+
+ return true;
+ }
+
+ private static void AppendValueIfRequired(StringBuilder sb, bool appendValue, string value)
+ {
+ if (appendValue)
+ {
+ AppendValueWithSeparatorIfRequired(sb, value);
+ }
+ }
+
+ private static void AppendValueWithSeparatorIfRequired(StringBuilder sb, string value)
+ {
+ if (sb.Length > 0)
+ {
+ sb.Append(", ");
+ }
+ sb.Append(value);
+ }
+
+ private static void AppendValues(StringBuilder sb, IEnumerable values)
+ {
+ var first = true;
+ foreach (var value in values)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ sb.Append(", ");
+ }
+
+ sb.Append(value);
+ }
+ }
+
+ private static void CheckIsValidToken(StringSegment item)
+ {
+ HeaderUtilities.CheckValidToken(item, nameof(item));
+ }
+ }
+}
diff --git a/src/Http/Headers/src/ContentDispositionHeaderValue.cs b/src/Http/Headers/src/ContentDispositionHeaderValue.cs
new file mode 100644
index 0000000000..b9292ac1a8
--- /dev/null
+++ b/src/Http/Headers/src/ContentDispositionHeaderValue.cs
@@ -0,0 +1,725 @@
+// 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.Buffers;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+ // Note this is for use both in HTTP (https://tools.ietf.org/html/rfc6266) and MIME (https://tools.ietf.org/html/rfc2183)
+ public class ContentDispositionHeaderValue
+ {
+ private const string FileNameString = "filename";
+ private const string NameString = "name";
+ private const string FileNameStarString = "filename*";
+ private const string CreationDateString = "creation-date";
+ private const string ModificationDateString = "modification-date";
+ private const string ReadDateString = "read-date";
+ private const string SizeString = "size";
+ private static readonly char[] QuestionMark = new char[] { '?' };
+ private static readonly char[] SingleQuote = new char[] { '\'' };
+
+ private static readonly HttpHeaderParser Parser
+ = new GenericHeaderParser(false, GetDispositionTypeLength);
+
+ // Use list instead of dictionary since we may have multiple parameters with the same name.
+ private ObjectCollection _parameters;
+ private StringSegment _dispositionType;
+
+ private ContentDispositionHeaderValue()
+ {
+ // Used by the parser to create a new instance of this type.
+ }
+
+ public ContentDispositionHeaderValue(StringSegment dispositionType)
+ {
+ CheckDispositionTypeFormat(dispositionType, "dispositionType");
+ _dispositionType = dispositionType;
+ }
+
+ public StringSegment DispositionType
+ {
+ get { return _dispositionType; }
+ set
+ {
+ CheckDispositionTypeFormat(value, "value");
+ _dispositionType = value;
+ }
+ }
+
+ public IList Parameters
+ {
+ get
+ {
+ if (_parameters == null)
+ {
+ _parameters = new ObjectCollection();
+ }
+ return _parameters;
+ }
+ }
+
+ // Helpers to access specific parameters in the list
+
+ public StringSegment Name
+ {
+ get { return GetName(NameString); }
+ set { SetName(NameString, value); }
+ }
+
+
+ public StringSegment FileName
+ {
+ get { return GetName(FileNameString); }
+ set { SetName(FileNameString, value); }
+ }
+
+ public StringSegment FileNameStar
+ {
+ get { return GetName(FileNameStarString); }
+ set { SetName(FileNameStarString, value); }
+ }
+
+ public DateTimeOffset? CreationDate
+ {
+ get { return GetDate(CreationDateString); }
+ set { SetDate(CreationDateString, value); }
+ }
+
+ public DateTimeOffset? ModificationDate
+ {
+ get { return GetDate(ModificationDateString); }
+ set { SetDate(ModificationDateString, value); }
+ }
+
+ public DateTimeOffset? ReadDate
+ {
+ get { return GetDate(ReadDateString); }
+ set { SetDate(ReadDateString, value); }
+ }
+
+ public long? Size
+ {
+ get
+ {
+ var sizeParameter = NameValueHeaderValue.Find(_parameters, SizeString);
+ long value;
+ if (sizeParameter != null)
+ {
+ var sizeString = sizeParameter.Value;
+ if (HeaderUtilities.TryParseNonNegativeInt64(sizeString, out value))
+ {
+ return value;
+ }
+ }
+ return null;
+ }
+ set
+ {
+ var sizeParameter = NameValueHeaderValue.Find(_parameters, SizeString);
+ if (value == null)
+ {
+ // Remove parameter
+ if (sizeParameter != null)
+ {
+ _parameters.Remove(sizeParameter);
+ }
+ }
+ else if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ else if (sizeParameter != null)
+ {
+ sizeParameter.Value = value.Value.ToString(CultureInfo.InvariantCulture);
+ }
+ else
+ {
+ string sizeString = value.Value.ToString(CultureInfo.InvariantCulture);
+ _parameters.Add(new NameValueHeaderValue(SizeString, sizeString));
+ }
+ }
+ }
+
+ ///
+ /// Sets both FileName and FileNameStar using encodings appropriate for HTTP headers.
+ ///
+ ///
+ public void SetHttpFileName(StringSegment fileName)
+ {
+ if (!StringSegment.IsNullOrEmpty(fileName))
+ {
+ FileName = Sanatize(fileName);
+ }
+ else
+ {
+ FileName = fileName;
+ }
+ FileNameStar = fileName;
+ }
+
+ ///
+ /// Sets the FileName parameter using encodings appropriate for MIME headers.
+ /// The FileNameStar paraemter is removed.
+ ///
+ ///
+ public void SetMimeFileName(StringSegment fileName)
+ {
+ FileNameStar = null;
+ FileName = fileName;
+ }
+
+ public override string ToString()
+ {
+ return _dispositionType + NameValueHeaderValue.ToString(_parameters, ';', true);
+ }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as ContentDispositionHeaderValue;
+
+ if (other == null)
+ {
+ return false;
+ }
+
+ return _dispositionType.Equals(other._dispositionType, StringComparison.OrdinalIgnoreCase) &&
+ HeaderUtilities.AreEqualCollections(_parameters, other._parameters);
+ }
+
+ public override int GetHashCode()
+ {
+ // The dispositionType string is case-insensitive.
+ return StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_dispositionType) ^ NameValueHeaderValue.GetHashCode(_parameters);
+ }
+
+ public static ContentDispositionHeaderValue Parse(StringSegment input)
+ {
+ var index = 0;
+ return Parser.ParseValue(input, ref index);
+ }
+
+ public static bool TryParse(StringSegment input, out ContentDispositionHeaderValue parsedValue)
+ {
+ var index = 0;
+ return Parser.TryParseValue(input, ref index, out parsedValue);
+ }
+
+ private static int GetDispositionTypeLength(StringSegment input, int startIndex, out ContentDispositionHeaderValue parsedValue)
+ {
+ Contract.Requires(startIndex >= 0);
+
+ parsedValue = null;
+
+ if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+ {
+ return 0;
+ }
+
+ // Caller must remove leading whitespaces. If not, we'll return 0.
+ var dispositionTypeLength = GetDispositionTypeExpressionLength(input, startIndex, out var dispositionType);
+
+ if (dispositionTypeLength == 0)
+ {
+ return 0;
+ }
+
+ var current = startIndex + dispositionTypeLength;
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+ var contentDispositionHeader = new ContentDispositionHeaderValue();
+ contentDispositionHeader._dispositionType = dispositionType;
+
+ // If we're not done and we have a parameter delimiter, then we have a list of parameters.
+ if ((current < input.Length) && (input[current] == ';'))
+ {
+ current++; // skip delimiter.
+ int parameterLength = NameValueHeaderValue.GetNameValueListLength(input, current, ';',
+ contentDispositionHeader.Parameters);
+
+ parsedValue = contentDispositionHeader;
+ return current + parameterLength - startIndex;
+ }
+
+ // We have a ContentDisposition header without parameters.
+ parsedValue = contentDispositionHeader;
+ return current - startIndex;
+ }
+
+ private static int GetDispositionTypeExpressionLength(StringSegment input, int startIndex, out StringSegment dispositionType)
+ {
+ Contract.Requires((input != null) && (input.Length > 0) && (startIndex < input.Length));
+
+ // This method just parses the disposition type string, it does not parse parameters.
+ dispositionType = null;
+
+ // Parse the disposition type, i.e. in content-disposition string
+ // "; param1=value1; param2=value2"
+ var typeLength = HttpRuleParser.GetTokenLength(input, startIndex);
+
+ if (typeLength == 0)
+ {
+ return 0;
+ }
+
+ dispositionType = input.Subsegment(startIndex, typeLength);
+ return typeLength;
+ }
+
+ private static void CheckDispositionTypeFormat(StringSegment dispositionType, string parameterName)
+ {
+ if (StringSegment.IsNullOrEmpty(dispositionType))
+ {
+ throw new ArgumentException("An empty string is not allowed.", parameterName);
+ }
+
+ // When adding values using strongly typed objects, no leading/trailing LWS (whitespaces) are allowed.
+ var dispositionTypeLength = GetDispositionTypeExpressionLength(dispositionType, 0, out var tempDispositionType);
+ if ((dispositionTypeLength == 0) || (tempDispositionType.Length != dispositionType.Length))
+ {
+ throw new FormatException(string.Format(CultureInfo.InvariantCulture,
+ "Invalid disposition type '{0}'.", dispositionType));
+ }
+ }
+
+ // Gets a parameter of the given name and attempts to extract a date.
+ // Returns null if the parameter is not present or the format is incorrect.
+ private DateTimeOffset? GetDate(string parameter)
+ {
+ var dateParameter = NameValueHeaderValue.Find(_parameters, parameter);
+ if (dateParameter != null)
+ {
+ var dateString = dateParameter.Value;
+ // Should have quotes, remove them.
+ if (IsQuoted(dateString))
+ {
+ dateString = dateString.Subsegment(1, dateString.Length - 2);
+ }
+ DateTimeOffset date;
+ if (HttpRuleParser.TryStringToDate(dateString, out date))
+ {
+ return date;
+ }
+ }
+ return null;
+ }
+
+ // Add the given parameter to the list. Remove if date is null.
+ private void SetDate(string parameter, DateTimeOffset? date)
+ {
+ var dateParameter = NameValueHeaderValue.Find(_parameters, parameter);
+ if (date == null)
+ {
+ // Remove parameter
+ if (dateParameter != null)
+ {
+ _parameters.Remove(dateParameter);
+ }
+ }
+ else
+ {
+ // Must always be quoted
+ var dateString = HeaderUtilities.FormatDate(date.Value, quoted: true);
+ if (dateParameter != null)
+ {
+ dateParameter.Value = dateString;
+ }
+ else
+ {
+ Parameters.Add(new NameValueHeaderValue(parameter, dateString));
+ }
+ }
+ }
+
+ // Gets a parameter of the given name and attempts to decode it if necessary.
+ // Returns null if the parameter is not present or the raw value if the encoding is incorrect.
+ private StringSegment GetName(string parameter)
+ {
+ var nameParameter = NameValueHeaderValue.Find(_parameters, parameter);
+ if (nameParameter != null)
+ {
+ string result;
+ // filename*=utf-8'lang'%7FMyString
+ if (parameter.EndsWith("*", StringComparison.Ordinal))
+ {
+ if (TryDecode5987(nameParameter.Value, out result))
+ {
+ return result;
+ }
+ return null; // Unrecognized encoding
+ }
+
+ // filename="=?utf-8?B?BDFSDFasdfasdc==?="
+ if (TryDecodeMime(nameParameter.Value, out result))
+ {
+ return result;
+ }
+ // May not have been encoded
+ return HeaderUtilities.RemoveQuotes(nameParameter.Value);
+ }
+ return null;
+ }
+
+ // Add/update the given parameter in the list, encoding if necessary.
+ // Remove if value is null/Empty
+ private void SetName(StringSegment parameter, StringSegment value)
+ {
+ var nameParameter = NameValueHeaderValue.Find(_parameters, parameter);
+ if (StringSegment.IsNullOrEmpty(value))
+ {
+ // Remove parameter
+ if (nameParameter != null)
+ {
+ _parameters.Remove(nameParameter);
+ }
+ }
+ else
+ {
+ var processedValue = StringSegment.Empty;
+ if (parameter.EndsWith("*", StringComparison.Ordinal))
+ {
+ processedValue = Encode5987(value);
+ }
+ else
+ {
+ processedValue = EncodeAndQuoteMime(value);
+ }
+
+ if (nameParameter != null)
+ {
+ nameParameter.Value = processedValue;
+ }
+ else
+ {
+ Parameters.Add(new NameValueHeaderValue(parameter, processedValue));
+ }
+ }
+ }
+
+ // Returns input for decoding failures, as the content might not be encoded
+ private StringSegment EncodeAndQuoteMime(StringSegment input)
+ {
+ var result = input;
+ var needsQuotes = false;
+ // Remove bounding quotes, they'll get re-added later
+ if (IsQuoted(result))
+ {
+ result = result.Subsegment(1, result.Length - 2);
+ needsQuotes = true;
+ }
+
+ if (RequiresEncoding(result))
+ {
+ needsQuotes = true; // Encoded data must always be quoted, the equals signs are invalid in tokens
+ result = EncodeMime(result); // =?utf-8?B?asdfasdfaesdf?=
+ }
+ else if (!needsQuotes && HttpRuleParser.GetTokenLength(result, 0) != result.Length)
+ {
+ needsQuotes = true;
+ }
+
+ if (needsQuotes)
+ {
+ // '\' and '"' must be escaped in a quoted string
+ result = result.ToString().Replace(@"\", @"\\").Replace(@"""", @"\""");
+ // Re-add quotes "value"
+ result = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", result);
+ }
+ return result;
+ }
+
+ // Replaces characters not suitable for HTTP headers with '_' rather than MIME encoding them.
+ private StringSegment Sanatize(StringSegment input)
+ {
+ var result = input;
+
+ if (RequiresEncoding(result))
+ {
+ var builder = new StringBuilder(result.Length);
+ for (int i = 0; i < result.Length; i++)
+ {
+ var c = result[i];
+ if ((int)c > 0x7f)
+ {
+ c = '_'; // Replace out-of-range characters
+ }
+ builder.Append(c);
+ }
+ result = builder.ToString();
+ }
+
+ return result;
+ }
+
+ // Returns true if the value starts and ends with a quote
+ private bool IsQuoted(StringSegment value)
+ {
+ Contract.Assert(value != null);
+
+ return value.Length > 1 && value.StartsWith("\"", StringComparison.Ordinal)
+ && value.EndsWith("\"", StringComparison.Ordinal);
+ }
+
+ // tspecials are required to be in a quoted string. Only non-ascii needs to be encoded.
+ private bool RequiresEncoding(StringSegment input)
+ {
+ Contract.Assert(input != null);
+
+ for (int i = 0; i < input.Length; i++)
+ {
+ if ((int)input[i] > 0x7f)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Encode using MIME encoding
+ private unsafe string EncodeMime(StringSegment input)
+ {
+ fixed (char* chars = input.Buffer)
+ {
+ var byteCount = Encoding.UTF8.GetByteCount(chars + input.Offset, input.Length);
+ var buffer = new byte[byteCount];
+ fixed (byte* bytes = buffer)
+ {
+ Encoding.UTF8.GetBytes(chars + input.Offset, input.Length, bytes, byteCount);
+ }
+ var encodedName = Convert.ToBase64String(buffer);
+ return "=?utf-8?B?" + encodedName + "?=";
+ }
+ }
+
+ // Attempt to decode MIME encoded strings
+ private bool TryDecodeMime(StringSegment input, out string output)
+ {
+ Contract.Assert(input != null);
+
+ output = null;
+ var processedInput = input;
+ // Require quotes, min of "=?e?b??="
+ if (!IsQuoted(processedInput) || processedInput.Length < 10)
+ {
+ return false;
+ }
+
+ var parts = processedInput.Split(QuestionMark).ToArray();
+ // "=, encodingName, encodingType, encodedData, ="
+ if (parts.Length != 5 || parts[0] != "\"=" || parts[4] != "=\""
+ || !parts[2].Equals("b", StringComparison.OrdinalIgnoreCase))
+ {
+ // Not encoded.
+ // This does not support multi-line encoding.
+ // Only base64 encoding is supported, not quoted printable
+ return false;
+ }
+
+ try
+ {
+ var encoding = Encoding.GetEncoding(parts[1].ToString());
+ var bytes = Convert.FromBase64String(parts[3].ToString());
+ output = encoding.GetString(bytes, 0, bytes.Length);
+ return true;
+ }
+ catch (ArgumentException)
+ {
+ // Unknown encoding or bad characters
+ }
+ catch (FormatException)
+ {
+ // Bad base64 decoding
+ }
+ return false;
+ }
+
+ // Encode a string using RFC 5987 encoding
+ // encoding'lang'PercentEncodedSpecials
+ private string Encode5987(StringSegment input)
+ {
+ var builder = new StringBuilder("UTF-8\'\'");
+ for (int i = 0; i < input.Length; i++)
+ {
+ var c = input[i];
+ // attr-char = ALPHA / DIGIT / "!" / "#" / "$" / "&" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
+ // ; token except ( "*" / "'" / "%" )
+ if (c > 0x7F) // Encodes as multiple utf-8 bytes
+ {
+ var bytes = Encoding.UTF8.GetBytes(c.ToString());
+ foreach (byte b in bytes)
+ {
+ HexEscape(builder, (char)b);
+ }
+ }
+ else if (!HttpRuleParser.IsTokenChar(c) || c == '*' || c == '\'' || c == '%')
+ {
+ // ASCII - Only one encoded byte
+ HexEscape(builder, c);
+ }
+ else
+ {
+ builder.Append(c);
+ }
+ }
+ return builder.ToString();
+ }
+
+ private static readonly char[] HexUpperChars = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+ private static void HexEscape(StringBuilder builder, char c)
+ {
+ builder.Append('%');
+ builder.Append(HexUpperChars[(c & 0xf0) >> 4]);
+ builder.Append(HexUpperChars[c & 0xf]);
+ }
+
+ // Attempt to decode using RFC 5987 encoding.
+ // encoding'language'my%20string
+ private bool TryDecode5987(StringSegment input, out string output)
+ {
+ output = null;
+
+ var parts = input.Split(SingleQuote).ToArray();
+ if (parts.Length != 3)
+ {
+ return false;
+ }
+
+ var decoded = new StringBuilder();
+ byte[] unescapedBytes = null;
+ try
+ {
+ var encoding = Encoding.GetEncoding(parts[0].ToString());
+
+ var dataString = parts[2];
+ unescapedBytes = ArrayPool.Shared.Rent(dataString.Length);
+ var unescapedBytesCount = 0;
+ for (var index = 0; index < dataString.Length; index++)
+ {
+ if (IsHexEncoding(dataString, index)) // %FF
+ {
+ // Unescape and cache bytes, multi-byte characters must be decoded all at once
+ unescapedBytes[unescapedBytesCount++] = HexUnescape(dataString, ref index);
+ index--; // HexUnescape did +=3; Offset the for loop's ++
+ }
+ else
+ {
+ if (unescapedBytesCount > 0)
+ {
+ // Decode any previously cached bytes
+ decoded.Append(encoding.GetString(unescapedBytes, 0, unescapedBytesCount));
+ unescapedBytesCount = 0;
+ }
+ decoded.Append(dataString[index]); // Normal safe character
+ }
+ }
+
+ if (unescapedBytesCount > 0)
+ {
+ // Decode any previously cached bytes
+ decoded.Append(encoding.GetString(unescapedBytes, 0, unescapedBytesCount));
+ }
+ }
+ catch (ArgumentException)
+ {
+ return false; // Unknown encoding or bad characters
+ }
+ finally
+ {
+ if (unescapedBytes != null)
+ {
+ ArrayPool.Shared.Return(unescapedBytes);
+ }
+ }
+
+ output = decoded.ToString();
+ return true;
+ }
+
+ private static bool IsHexEncoding(StringSegment pattern, int index)
+ {
+ if ((pattern.Length - index) < 3)
+ {
+ return false;
+ }
+ if ((pattern[index] == '%') && IsEscapedAscii(pattern[index + 1], pattern[index + 2]))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ private static bool IsEscapedAscii(char digit, char next)
+ {
+ if (!(((digit >= '0') && (digit <= '9'))
+ || ((digit >= 'A') && (digit <= 'F'))
+ || ((digit >= 'a') && (digit <= 'f'))))
+ {
+ return false;
+ }
+
+ if (!(((next >= '0') && (next <= '9'))
+ || ((next >= 'A') && (next <= 'F'))
+ || ((next >= 'a') && (next <= 'f'))))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static byte HexUnescape(StringSegment pattern, ref int index)
+ {
+ if ((index < 0) || (index >= pattern.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ if ((pattern[index] == '%')
+ && (pattern.Length - index >= 3))
+ {
+ var ret = UnEscapeAscii(pattern[index + 1], pattern[index + 2]);
+ index += 3;
+ return ret;
+ }
+ return (byte)pattern[index++];
+ }
+
+ internal static byte UnEscapeAscii(char digit, char next)
+ {
+ if (!(((digit >= '0') && (digit <= '9'))
+ || ((digit >= 'A') && (digit <= 'F'))
+ || ((digit >= 'a') && (digit <= 'f'))))
+ {
+ throw new ArgumentException();
+ }
+
+ var res = (digit <= '9')
+ ? ((int)digit - (int)'0')
+ : (((digit <= 'F')
+ ? ((int)digit - (int)'A')
+ : ((int)digit - (int)'a'))
+ + 10);
+
+ if (!(((next >= '0') && (next <= '9'))
+ || ((next >= 'A') && (next <= 'F'))
+ || ((next >= 'a') && (next <= 'f'))))
+ {
+ throw new ArgumentException();
+ }
+
+ return (byte)((res << 4) + ((next <= '9')
+ ? ((int)next - (int)'0')
+ : (((next <= 'F')
+ ? ((int)next - (int)'A')
+ : ((int)next - (int)'a'))
+ + 10)));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Http/Headers/src/ContentDispositionHeaderValueIdentityExtensions.cs b/src/Http/Headers/src/ContentDispositionHeaderValueIdentityExtensions.cs
new file mode 100644
index 0000000000..9ef74baa0c
--- /dev/null
+++ b/src/Http/Headers/src/ContentDispositionHeaderValueIdentityExtensions.cs
@@ -0,0 +1,46 @@
+// 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.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+ ///
+ /// Various extension methods for for identifying the type of the disposition header
+ ///
+ public static class ContentDispositionHeaderValueIdentityExtensions
+ {
+ ///
+ /// Checks if the content disposition header is a file disposition
+ ///
+ /// The header to check
+ /// True if the header is file disposition, false otherwise
+ public static bool IsFileDisposition(this ContentDispositionHeaderValue header)
+ {
+ if (header == null)
+ {
+ throw new ArgumentNullException(nameof(header));
+ }
+
+ return header.DispositionType.Equals("form-data")
+ && (!StringSegment.IsNullOrEmpty(header.FileName) || !StringSegment.IsNullOrEmpty(header.FileNameStar));
+ }
+
+ ///
+ /// Checks if the content disposition header is a form disposition
+ ///
+ /// The header to check
+ /// True if the header is form disposition, false otherwise
+ public static bool IsFormDisposition(this ContentDispositionHeaderValue header)
+ {
+ if (header == null)
+ {
+ throw new ArgumentNullException(nameof(header));
+ }
+
+ return header.DispositionType.Equals("form-data")
+ && StringSegment.IsNullOrEmpty(header.FileName) && StringSegment.IsNullOrEmpty(header.FileNameStar);
+ }
+ }
+}
diff --git a/src/Http/Headers/src/ContentRangeHeaderValue.cs b/src/Http/Headers/src/ContentRangeHeaderValue.cs
new file mode 100644
index 0000000000..99583cdf47
--- /dev/null
+++ b/src/Http/Headers/src/ContentRangeHeaderValue.cs
@@ -0,0 +1,407 @@
+// 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.Diagnostics.Contracts;
+using System.Globalization;
+using System.Text;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+ public class ContentRangeHeaderValue
+ {
+ private static readonly HttpHeaderParser Parser
+ = new GenericHeaderParser(false, GetContentRangeLength);
+
+ private StringSegment _unit;
+ private long? _from;
+ private long? _to;
+ private long? _length;
+
+ private ContentRangeHeaderValue()
+ {
+ // Used by the parser to create a new instance of this type.
+ }
+
+ public ContentRangeHeaderValue(long from, long to, long length)
+ {
+ // Scenario: "Content-Range: bytes 12-34/5678"
+
+ if (length < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length));
+ }
+ if ((to < 0) || (to > length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(to));
+ }
+ if ((from < 0) || (from > to))
+ {
+ throw new ArgumentOutOfRangeException(nameof(from));
+ }
+
+ _from = from;
+ _to = to;
+ _length = length;
+ _unit = HeaderUtilities.BytesUnit;
+ }
+
+ public ContentRangeHeaderValue(long length)
+ {
+ // Scenario: "Content-Range: bytes */1234"
+
+ if (length < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length));
+ }
+
+ _length = length;
+ _unit = HeaderUtilities.BytesUnit;
+ }
+
+ public ContentRangeHeaderValue(long from, long to)
+ {
+ // Scenario: "Content-Range: bytes 12-34/*"
+
+ if (to < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(to));
+ }
+ if ((from < 0) || (from > to))
+ {
+ throw new ArgumentOutOfRangeException(nameof(@from));
+ }
+
+ _from = from;
+ _to = to;
+ _unit = HeaderUtilities.BytesUnit;
+ }
+
+ public StringSegment Unit
+ {
+ get { return _unit; }
+ set
+ {
+ HeaderUtilities.CheckValidToken(value, nameof(value));
+ _unit = value;
+ }
+ }
+
+ public long? From
+ {
+ get { return _from; }
+ }
+
+ public long? To
+ {
+ get { return _to; }
+ }
+
+ public long? Length
+ {
+ get { return _length; }
+ }
+
+ public bool HasLength // e.g. "Content-Range: bytes 12-34/*"
+ {
+ get { return _length != null; }
+ }
+
+ public bool HasRange // e.g. "Content-Range: bytes */1234"
+ {
+ get { return _from != null; }
+ }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as ContentRangeHeaderValue;
+
+ if (other == null)
+ {
+ return false;
+ }
+
+ return ((_from == other._from) && (_to == other._to) && (_length == other._length) &&
+ StringSegment.Equals(_unit, other._unit, StringComparison.OrdinalIgnoreCase));
+ }
+
+ public override int GetHashCode()
+ {
+ var result = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_unit);
+
+ if (HasRange)
+ {
+ result = result ^ _from.GetHashCode() ^ _to.GetHashCode();
+ }
+
+ if (HasLength)
+ {
+ result = result ^ _length.GetHashCode();
+ }
+
+ return result;
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.Append(_unit);
+ sb.Append(' ');
+
+ if (HasRange)
+ {
+ sb.Append(_from.Value.ToString(NumberFormatInfo.InvariantInfo));
+ sb.Append('-');
+ sb.Append(_to.Value.ToString(NumberFormatInfo.InvariantInfo));
+ }
+ else
+ {
+ sb.Append('*');
+ }
+
+ sb.Append('/');
+ if (HasLength)
+ {
+ sb.Append(_length.Value.ToString(NumberFormatInfo.InvariantInfo));
+ }
+ else
+ {
+ sb.Append('*');
+ }
+
+ return sb.ToString();
+ }
+
+ public static ContentRangeHeaderValue Parse(StringSegment input)
+ {
+ var index = 0;
+ return Parser.ParseValue(input, ref index);
+ }
+
+ public static bool TryParse(StringSegment input, out ContentRangeHeaderValue parsedValue)
+ {
+ var index = 0;
+ return Parser.TryParseValue(input, ref index, out parsedValue);
+ }
+
+ private static int GetContentRangeLength(StringSegment input, int startIndex, out ContentRangeHeaderValue parsedValue)
+ {
+ Contract.Requires(startIndex >= 0);
+
+ parsedValue = null;
+
+ if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+ {
+ return 0;
+ }
+
+ // Parse the unit string: in ' -/'
+ var unitLength = HttpRuleParser.GetTokenLength(input, startIndex);
+
+ if (unitLength == 0)
+ {
+ return 0;
+ }
+
+ var unit = input.Subsegment(startIndex, unitLength);
+ var current = startIndex + unitLength;
+ var separatorLength = HttpRuleParser.GetWhitespaceLength(input, current);
+
+ if (separatorLength == 0)
+ {
+ return 0;
+ }
+
+ current = current + separatorLength;
+
+ if (current == input.Length)
+ {
+ return 0;
+ }
+
+ // Read range values and in ' -/'
+ var fromStartIndex = current;
+ var fromLength = 0;
+ var toStartIndex = 0;
+ var toLength = 0;
+ if (!TryGetRangeLength(input, ref current, out fromLength, out toStartIndex, out toLength))
+ {
+ return 0;
+ }
+
+ // After the range is read we expect the length separator '/'
+ if ((current == input.Length) || (input[current] != '/'))
+ {
+ return 0;
+ }
+
+ current++; // Skip '/' separator
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+ if (current == input.Length)
+ {
+ return 0;
+ }
+
+ // We may not have a length (e.g. 'bytes 1-2/*'). But if we do, parse the length now.
+ var lengthStartIndex = current;
+ var lengthLength = 0;
+ if (!TryGetLengthLength(input, ref current, out lengthLength))
+ {
+ return 0;
+ }
+
+ if (!TryCreateContentRange(input, unit, fromStartIndex, fromLength, toStartIndex, toLength,
+ lengthStartIndex, lengthLength, out parsedValue))
+ {
+ return 0;
+ }
+
+ return current - startIndex;
+ }
+
+ private static bool TryGetLengthLength(StringSegment input, ref int current, out int lengthLength)
+ {
+ lengthLength = 0;
+
+ if (input[current] == '*')
+ {
+ current++;
+ }
+ else
+ {
+ // Parse length value: