Add docs for Auth, Auth.Cookies, Auth.Certificate (#26503)

* Add docs for Auth, Auth.Cookies, Auth.Certificate

Contributes to https://github.com/dotnet/aspnetcore/issues/26397

* Also add JWT

* Apply suggestions from code review

Co-authored-by: Hao Kung <HaoK@users.noreply.github.com>
Co-authored-by: Chris Ross <Tratcher@Outlook.com>

* Update src/Security/Authentication/Core/src/TicketSerializer.cs

* Update src/Security/Authentication/Core/src/TicketSerializer.cs

* Update src/Security/Authentication/Core/src/TicketSerializer.cs

* Apply suggestions from code review

Co-authored-by: Hao Kung <HaoK@users.noreply.github.com>
Co-authored-by: Chris Ross <Tratcher@Outlook.com>
This commit is contained in:
Pranav K 2020-10-02 13:12:16 -07:00 committed by GitHub
parent acf1dc45c0
commit e755f6017c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 650 additions and 57 deletions

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Certificate;
@ -15,6 +16,11 @@ namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Adds certificate authentication.
/// <para>
/// Certificate authentication uses a authentication handler that validates client certificate and
/// raises an event where the certificate is resolved to a <see cref="ClaimsPrincipal"/>.
/// See https://tools.ietf.org/html/rfc5246#section-7.4.4 to read more about certicate authentication.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <returns>The <see cref="AuthenticationBuilder"/>.</returns>
@ -23,6 +29,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// <summary>
/// Adds certificate authentication.
/// <para>
/// Certificate authentication uses a authentication handler that validates client certificate and
/// raises an event where the certificate is resolved to a <see cref="ClaimsPrincipal"/>.
/// See https://tools.ietf.org/html/rfc5246#section-7.4.4 to read more about certicate authentication.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="authenticationScheme"></param>
@ -32,6 +43,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// <summary>
/// Adds certificate authentication.
/// <para>
/// Certificate authentication uses a authentication handler that validates client certificate and
/// raises an event where the certificate is resolved to a <see cref="ClaimsPrincipal"/>.
/// See https://tools.ietf.org/html/rfc5246#section-7.4.4 to read more about certicate authentication.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="configureOptions"></param>
@ -41,6 +57,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// <summary>
/// Adds certificate authentication.
/// <para>
/// Certificate authentication uses a authentication handler that validates client certificate and
/// raises an event where the certificate is resolved to a <see cref="ClaimsPrincipal"/>.
/// See https://tools.ietf.org/html/rfc5246#section-7.4.4 to read more about certicate authentication.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="authenticationScheme"></param>
@ -54,6 +75,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// <summary>
/// Adds certificate authentication.
/// <para>
/// Certificate authentication uses a authentication handler that validates client certificate and
/// raises an event where the certificate is resolved to a <see cref="ClaimsPrincipal"/>.
/// See https://tools.ietf.org/html/rfc5246#section-7.4.4 to read more about certicate authentication.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="configureOptions"></param>

View File

@ -13,6 +13,9 @@ namespace Microsoft.AspNetCore.Authentication.Certificate
/// <summary>
/// Value indicating the types of certificates accepted by the authentication middleware.
/// </summary>
/// <value>
/// Defaults to <see cref="CertificateTypes.Chained"/>.
/// </value>
public CertificateTypes AllowedCertificateTypes { get; set; } = CertificateTypes.Chained;
/// <summary>
@ -23,6 +26,9 @@ namespace Microsoft.AspNetCore.Authentication.Certificate
/// <summary>
/// Method used to validate certificate chains against <see cref="CustomTrustStore"/>.
/// </summary>
/// <value>
/// Defaults to <see cref="X509ChainTrustMode.System"/>.
/// </value>
/// <remarks>This property must be set to <see cref="X509ChainTrustMode.CustomRootTrust"/> to enable <see cref="CustomTrustStore"/> to be used in certificate chain validation.</remarks>
public X509ChainTrustMode ChainTrustValidationMode { get; set; } = X509ChainTrustMode.System;
@ -32,21 +38,33 @@ namespace Microsoft.AspNetCore.Authentication.Certificate
/// at all. If the certificate chains to a root CA all certificates in the chain must be validated
/// for the client authentication EKU.
/// </summary>
/// <value>
/// Defaults to <see langword="true" />.
/// </value>
public bool ValidateCertificateUse { get; set; } = true;
/// <summary>
/// Flag indicating whether the client certificate validity period should be checked.
/// </summary>
/// <value>
/// Defaults to <see langword="true" />.
/// </value>
public bool ValidateValidityPeriod { get; set; } = true;
/// <summary>
/// Specifies which X509 certificates in the chain should be checked for revocation.
/// </summary>
/// <value>
/// Defaults to <see cref="X509RevocationFlag.ExcludeRoot" />.
/// </value>
public X509RevocationFlag RevocationFlag { get; set; } = X509RevocationFlag.ExcludeRoot;
/// <summary>
/// Specifies conditions under which verification of certificates in the X509 chain should be conducted.
/// </summary>
/// <value>
/// Defaults to <see cref="X509RevocationMode.Online" />.
/// </value>
public X509RevocationMode RevocationMode { get; set; } = X509RevocationMode.Online;
/// <summary>

View File

@ -14,9 +14,13 @@ namespace Microsoft.AspNetCore.Authentication.Certificate
/// </summary>
public class CertificateValidationCache : ICertificateValidationCache
{
private MemoryCache _cache;
private CertificateValidationCacheOptions _options;
private readonly MemoryCache _cache;
private readonly CertificateValidationCacheOptions _options;
/// <summary>
/// Initializes a new instance of <see cref="CertificateValidationCache"/>.
/// </summary>
/// <param name="options">An accessor to <see cref="CertificateValidationCacheOptions"/></param>
public CertificateValidationCache(IOptions<CertificateValidationCacheOptions> options)
{
_options = options.Value;

View File

@ -1,9 +1,6 @@
// 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.
// 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.Certificate
@ -14,14 +11,18 @@ namespace Microsoft.AspNetCore.Authentication.Certificate
public class CertificateValidationCacheOptions
{
/// <summary>
/// The expiration that should be used for entries in the MemoryCache, defaults to 2 minutes.
/// Gets or sets the expiration that should be used for entries in the MemoryCache.
/// This is a sliding expiration that will extend each time the certificate is used, so long as the certificate is valid (see X509Certificate2.NotAfter).
/// </summary>
/// <value>Defaults to 2 minutes.</value>
public TimeSpan CacheEntryExpiration { get; set; } = TimeSpan.FromMinutes(2);
/// <summary>
/// How many validated certificate results to store in the cache, defaults to 1024.
/// Gets or sets the maximum number of validated certificate results that are allowed to cached.
/// </summary>
/// <value>
/// Defaults to 1024.
/// </value>
public int CacheSize { get; set; } = 1024;
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>ASP.NET Core middleware that enables an application to support certificate authentication.</Description>
@ -6,6 +6,7 @@
<DefineConstants>$(DefineConstants);SECURITY</DefineConstants>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;authentication;security;x509;certificate</PackageTags>
<NoWarn>$(NoWarn.Replace('1591', ''))</NoWarn>
</PropertyGroup>
<ItemGroup>

View File

@ -9,34 +9,32 @@ using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Authentication.Cookies
{
/// <summary>
/// This default implementation of the ICookieAuthenticationEvents may be used if the
/// application only needs to override a few of the interface methods. This may be used as a base class
/// or may be instantiated directly.
/// Allows subscribing to events raised during cookie authentication.
/// </summary>
public class CookieAuthenticationEvents
{
/// <summary>
/// A delegate assigned to this property will be invoked when the related method is called.
/// Invoked to validate the principal.
/// </summary>
public Func<CookieValidatePrincipalContext, Task> OnValidatePrincipal { get; set; } = context => Task.CompletedTask;
/// <summary>
/// A delegate assigned to this property will be invoked when the related method is called.
/// Invoked on signing in.
/// </summary>
public Func<CookieSigningInContext, Task> OnSigningIn { get; set; } = context => Task.CompletedTask;
/// <summary>
/// A delegate assigned to this property will be invoked when the related method is called.
/// Invoked after sign in has completed.
/// </summary>
public Func<CookieSignedInContext, Task> OnSignedIn { get; set; } = context => Task.CompletedTask;
/// <summary>
/// A delegate assigned to this property will be invoked when the related method is called.
/// Invoked on signing out.
/// </summary>
public Func<CookieSigningOutContext, Task> OnSigningOut { get; set; } = context => Task.CompletedTask;
/// <summary>
/// A delegate assigned to this property will be invoked when the related method is called.
/// Invoked when the client needs to be redirected to the sign in url.
/// </summary>
public Func<RedirectContext<CookieAuthenticationOptions>, Task> OnRedirectToLogin { get; set; } = context =>
{
@ -53,7 +51,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
};
/// <summary>
/// A delegate assigned to this property will be invoked when the related method is called.
/// Invoked when the client needs to be redirected to the access denied url.
/// </summary>
public Func<RedirectContext<CookieAuthenticationOptions>, Task> OnRedirectToAccessDenied { get; set; } = context =>
{
@ -70,7 +68,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
};
/// <summary>
/// A delegate assigned to this property will be invoked when the related method is called.
/// Invoked when the client is to be redirected to logout.
/// </summary>
public Func<RedirectContext<CookieAuthenticationOptions>, Task> OnRedirectToLogout { get; set; } = context =>
{
@ -86,7 +84,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
};
/// <summary>
/// A delegate assigned to this property will be invoked when the related method is called.
/// Invoked when the client is to be redirected after logout.
/// </summary>
public Func<RedirectContext<CookieAuthenticationOptions>, Task> OnRedirectToReturnUrl { get; set; } = context =>
{
@ -108,52 +106,51 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
}
/// <summary>
/// Implements the interface method by invoking the related delegate method.
/// Invoked to validate the prinicipal.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
/// <param name="context">The <see cref="CookieValidatePrincipalContext"/>.</param>
public virtual Task ValidatePrincipal(CookieValidatePrincipalContext context) => OnValidatePrincipal(context);
/// <summary>
/// Implements the interface method by invoking the related delegate method.
/// Invoked during sign in.
/// </summary>
/// <param name="context"></param>
/// <param name="context">The <see cref="CookieSigningInContext"/>.</param>
public virtual Task SigningIn(CookieSigningInContext context) => OnSigningIn(context);
/// <summary>
/// Implements the interface method by invoking the related delegate method.
/// Invoked after sign in has completed.
/// </summary>
/// <param name="context"></param>
/// <param name="context">The <see cref="CookieSignedInContext"/>.</param>
public virtual Task SignedIn(CookieSignedInContext context) => OnSignedIn(context);
/// <summary>
/// Implements the interface method by invoking the related delegate method.
/// Invoked on sign out.
/// </summary>
/// <param name="context"></param>
/// <param name="context">The <see cref="CookieSigningOutContext"/>.</param>
public virtual Task SigningOut(CookieSigningOutContext context) => OnSigningOut(context);
/// <summary>
/// Implements the interface method by invoking the related delegate method.
/// Invoked when the client is being redirected to the log out url.
/// </summary>
/// <param name="context">Contains information about the event</param>
/// <param name="context">The <see cref="RedirectContext{TOptions}"/>.</param>
public virtual Task RedirectToLogout(RedirectContext<CookieAuthenticationOptions> context) => OnRedirectToLogout(context);
/// <summary>
/// Implements the interface method by invoking the related delegate method.
/// Invoked when the client is being redirected to the log in url.
/// </summary>
/// <param name="context">Contains information about the event</param>
/// <param name="context">The <see cref="RedirectContext{TOptions}"/>.</param>
public virtual Task RedirectToLogin(RedirectContext<CookieAuthenticationOptions> context) => OnRedirectToLogin(context);
/// <summary>
/// Implements the interface method by invoking the related delegate method.
/// Invoked when the client is being redirected after log out.
/// </summary>
/// <param name="context">Contains information about the event</param>
/// <param name="context">The <see cref="RedirectContext{TOptions}"/>.</param>
public virtual Task RedirectToReturnUrl(RedirectContext<CookieAuthenticationOptions> context) => OnRedirectToReturnUrl(context);
/// <summary>
/// Implements the interface method by invoking the related delegate method.
/// Invoked when the client is being redirected to the access denied url.
/// </summary>
/// <param name="context">Contains information about the event</param>
/// <param name="context">The <see cref="RedirectContext{TOptions}"/>.</param>
public virtual Task RedirectToAccessDenied(RedirectContext<CookieAuthenticationOptions> context) => OnRedirectToAccessDenied(context);
}
}

View File

@ -15,6 +15,9 @@ using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Authentication.Cookies
{
/// <summary>
/// Implementation for the cookie-based authentication handler.
/// </summary>
public class CookieAuthenticationHandler : SignInAuthenticationHandler<CookieAuthenticationOptions>
{
private const string HeaderValueNoCache = "no-cache";
@ -32,6 +35,13 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
private Task<AuthenticateResult>? _readCookieTask;
private AuthenticationTicket? _refreshTicket;
/// <summary>
/// Initializes a new instance of <see cref="CookieAuthenticationHandler"/>.
/// </summary>
/// <param name="options">Accessor to <see cref="CookieAuthenticationOptions"/>.</param>
/// <param name="logger">The <see cref="ILoggerFactory"/>.</param>
/// <param name="encoder">The <see cref="UrlEncoder"/>.</param>
/// <param name="clock">The <see cref="ISystemClock"/>.</param>
public CookieAuthenticationHandler(IOptionsMonitor<CookieAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{ }
@ -46,6 +56,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
set { base.Events = value; }
}
/// <inheritdoc />
protected override Task InitializeHandlerAsync()
{
// Cookies needs to finish the response
@ -169,6 +180,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
return AuthenticateResult.Success(ticket);
}
/// <inheritdoc />
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var result = await EnsureCookieTicket();
@ -203,6 +215,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
return cookieOptions;
}
/// <inheritdoc />
protected virtual async Task FinishResponseAsync()
{
// Only renew if requested, and neither sign in or sign out was called
@ -254,6 +267,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
}
}
/// <inheritdoc />
protected async override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties)
{
if (user == null)
@ -346,6 +360,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
Logger.AuthenticationSchemeSignedIn(Scheme.Name);
}
/// <inheritdoc />
protected async override Task HandleSignOutAsync(AuthenticationProperties? properties)
{
properties = properties ?? new AuthenticationProperties();
@ -426,6 +441,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
return path[0] == '/' && path[1] != '/' && path[1] != '\\';
}
/// <inheritdoc />
protected override async Task HandleForbiddenAsync(AuthenticationProperties properties)
{
var returnUrl = properties.RedirectUri;
@ -438,6 +454,7 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
await Events.RedirectToAccessDenied(redirectContext);
}
/// <inheritdoc />
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
var redirectUri = properties.RedirectUri;

View File

@ -9,20 +9,72 @@ using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Extension methods to configure cookie authentication.
/// </summary>
public static class CookieExtensions
{
/// <summary>
/// Adds cookie authentication to <see cref="AuthenticationBuilder"/> using the default scheme.
/// The default scheme is specified by <see cref="CookieAuthenticationDefaults.AuthenticationScheme"/>.
/// <para>
/// Cookie authentication uses a HTTP cookie persisted in the client to perform authentication.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <returns>A reference to <paramref name="builder"/> after the operation has completed.</returns>
public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder)
=> builder.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
/// <summary>
/// Adds cookie authentication to <see cref="AuthenticationBuilder"/> using the specified scheme.
/// <para>
/// Cookie authentication uses a HTTP cookie persisted in the client to perform authentication.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="authenticationScheme">The authentication scheme.</param>
/// <returns>A reference to <paramref name="builder"/> after the operation has completed.</returns>
public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, string authenticationScheme)
=> builder.AddCookie(authenticationScheme, configureOptions: null!);
/// <summary>
/// Adds cookie authentication to <see cref="AuthenticationBuilder"/> using the default scheme.
/// The default scheme is specified by <see cref="CookieAuthenticationDefaults.AuthenticationScheme"/>.
/// <para>
/// Cookie authentication uses a HTTP cookie persisted in the client to perform authentication.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="configureOptions">A delegate to configure <see cref="CookieAuthenticationOptions"/>.</param>
/// <returns>A reference to <paramref name="builder"/> after the operation has completed.</returns>
public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, Action<CookieAuthenticationOptions> configureOptions)
=> builder.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, configureOptions);
/// <summary>
/// Adds cookie authentication to <see cref="AuthenticationBuilder"/> using the specified scheme.
/// <para>
/// Cookie authentication uses a HTTP cookie persisted in the client to perform authentication.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="authenticationScheme">The authentication scheme.</param>
/// <param name="configureOptions">A delegate to configure <see cref="CookieAuthenticationOptions"/>.</param>
/// <returns>A reference to <paramref name="builder"/> after the operation has completed.</returns>
public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, string authenticationScheme, Action<CookieAuthenticationOptions> configureOptions)
=> builder.AddCookie(authenticationScheme, displayName: null, configureOptions: configureOptions);
/// <summary>
/// Adds cookie authentication to <see cref="AuthenticationBuilder"/> using the specified scheme.
/// <para>
/// Cookie authentication uses a HTTP cookie persisted in the client to perform authentication.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="authenticationScheme">The authentication scheme.</param>
/// <param name="displayName">A display name for the authentication handler.</param>
/// <param name="configureOptions">A delegate to configure <see cref="CookieAuthenticationOptions"/>.</param>
/// <returns>A reference to <paramref name="builder"/> after the operation has completed.</returns>
public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action<CookieAuthenticationOptions> configureOptions)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<CookieAuthenticationOptions>, PostConfigureCookieAuthenticationOptions>());

View File

@ -5,7 +5,7 @@
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<IsAspNetCoreApp>true</IsAspNetCoreApp>
<DefineConstants>$(DefineConstants);SECURITY</DefineConstants>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<NoWarn>$(NoWarn.Replace('1591', ''))</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;authentication;security</PackageTags>
<IsPackable>false</IsPackable>

View File

@ -14,6 +14,10 @@ namespace Microsoft.AspNetCore.Authentication.Cookies
{
private readonly IDataProtectionProvider _dp;
/// <summary>
/// Initializes a new instance of <see cref="PostConfigureCookieAuthenticationOptions"/>.
/// </summary>
/// <param name="dataProtection">The <see cref="IDataProtectionProvider"/>.</param>
public PostConfigureCookieAuthenticationOptions(IDataProtectionProvider dataProtection)
{
_dp = dataProtection;

View File

@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Authentication
public class AuthenticationBuilder
{
/// <summary>
/// Constructor.
/// Initializes a new instance of <see cref="AuthenticationBuilder"/>.
/// </summary>
/// <param name="services">The services being configured.</param>
public AuthenticationBuilder(IServiceCollection services)

View File

@ -11,34 +11,73 @@ using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Authentication
{
/// <summary>
/// An opinionated abstraction for implementing <see cref="IAuthenticationHandler"/>.
/// </summary>
/// <typeparam name="TOptions">The type for the options used to configure the authentication handler.</typeparam>
public abstract class AuthenticationHandler<TOptions> : IAuthenticationHandler where TOptions : AuthenticationSchemeOptions, new()
{
private Task<AuthenticateResult>? _authenticateTask;
/// <summary>
/// Gets or sets the <see cref="AuthenticationScheme"/> asssociated with this authentication handler.
/// </summary>
public AuthenticationScheme Scheme { get; private set; } = default!;
/// <summary>
/// Gets or sets the options associated with this authentication handler.
/// </summary>
public TOptions Options { get; private set; } = default!;
/// <summary>
/// Gets or sets the <see cref="HttpContext"/>.
/// </summary>
protected HttpContext Context { get; private set; } = default!;
/// <summary>
/// Gets the <see cref="HttpRequest"/> associated with the current request.
/// </summary>
protected HttpRequest Request
{
get => Context.Request;
}
/// <summary>
/// Gets the <see cref="HttpResponse" /> associated with the current request.
/// </summary>
protected HttpResponse Response
{
get => Context.Response;
}
/// <summary>
/// Gets the path as seen by the authentication middleware.
/// </summary>
protected PathString OriginalPath => Context.Features.Get<IAuthenticationFeature>()?.OriginalPath ?? Request.Path;
/// <summary>
/// Gets the path base as seen by the authentication middleware.
/// </summary>
protected PathString OriginalPathBase => Context.Features.Get<IAuthenticationFeature>()?.OriginalPathBase ?? Request.PathBase;
/// <summary>
/// Gets the <see cref="ILogger"/>.
/// </summary>
protected ILogger Logger { get; }
/// <summary>
/// Gets the <see cref="UrlEncoder"/>.
/// </summary>
protected UrlEncoder UrlEncoder { get; }
/// <summary>
/// Gets the <see cref="ISystemClock"/>.
/// </summary>
protected ISystemClock Clock { get; }
/// <summary>
/// Gets the <see cref="IOptionsMonitor{TOptions}"/> to detect changes to options.
/// </summary>
protected IOptionsMonitor<TOptions> OptionsMonitor { get; }
/// <summary>
@ -47,13 +86,29 @@ namespace Microsoft.AspNetCore.Authentication
/// </summary>
protected virtual object? Events { get; set; }
/// <summary>
/// Gets the issuer that should be used when any claims are issued.
/// </summary>
/// <value>
/// The <c>ClaimsIssuer</c> configured in <typeparamref name="TOptions"/>, if configured, otherwise <see cref="AuthenticationScheme.Name"/>.
/// </value>
protected virtual string ClaimsIssuer => Options.ClaimsIssuer ?? Scheme.Name;
/// <summary>
/// Gets the absolute current url.
/// </summary>
protected string CurrentUri
{
get => Request.Scheme + "://" + Request.Host + Request.PathBase + Request.Path + Request.QueryString;
}
/// <summary>
/// Initializes a new instance of <see cref="AuthenticationHandler{TOptions}"/>.
/// </summary>
/// <param name="options">The monitor for the options instance.</param>
/// <param name="logger">The <see cref="ILoggerFactory"/>.</param>
/// <param name="encoder">The <see cref="System.Text.Encodings.Web.UrlEncoder"/>.</param>
/// <param name="clock">The <see cref="ISystemClock"/>.</param>
protected AuthenticationHandler(IOptionsMonitor<TOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
{
Logger = logger.CreateLogger(this.GetType().FullName);
@ -113,9 +168,19 @@ namespace Microsoft.AspNetCore.Authentication
/// <returns>A task</returns>
protected virtual Task InitializeHandlerAsync() => Task.CompletedTask;
/// <summary>
/// Constructs an absolute url for the specified <paramref name="targetPath"/>.
/// </summary>
/// <param name="targetPath">The path.</param>
/// <returns>The absolute url.</returns>
protected string BuildRedirectUri(string targetPath)
=> Request.Scheme + "://" + Request.Host + OriginalPathBase + targetPath;
/// <summary>
/// Resolves the scheme that this authentication operation is forwarded to.
/// </summary>
/// <param name="scheme">The scheme to forward. One of ForwardAuthenticate, ForwardChallenge, ForwardForbid, ForwardSignIn, or ForwardSignOut.</param>
/// <returns>The forwarded scheme or <see langword="null"/>.</returns>
protected virtual string? ResolveTarget(string? scheme)
{
var target = scheme ?? Options.ForwardDefaultSelector?.Invoke(Context) ?? Options.ForwardDefault;
@ -126,6 +191,7 @@ namespace Microsoft.AspNetCore.Authentication
: target;
}
/// <inheritdoc />
public async Task<AuthenticateResult> AuthenticateAsync()
{
var target = ResolveTarget(Options.ForwardAuthenticate);
@ -186,6 +252,10 @@ namespace Microsoft.AspNetCore.Authentication
}
}
/// <summary>
/// Allows derived types to handle authentication.
/// </summary>
/// <returns>The <see cref="AuthenticateResult"/>.</returns>
protected abstract Task<AuthenticateResult> HandleAuthenticateAsync();
/// <summary>
@ -212,6 +282,7 @@ namespace Microsoft.AspNetCore.Authentication
return Task.CompletedTask;
}
/// <inheritdoc />
public async Task ChallengeAsync(AuthenticationProperties? properties)
{
var target = ResolveTarget(Options.ForwardChallenge);
@ -226,6 +297,7 @@ namespace Microsoft.AspNetCore.Authentication
Logger.AuthenticationSchemeChallenged(Scheme.Name);
}
/// <inheritdoc />
public async Task ForbidAsync(AuthenticationProperties? properties)
{
var target = ResolveTarget(Options.ForwardForbid);

View File

@ -8,10 +8,18 @@ using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Authentication
{
/// <summary>
/// Middleware that performs authentication.
/// </summary>
public class AuthenticationMiddleware
{
private readonly RequestDelegate _next;
/// <summary>
/// Initializes a new instance of <see cref="AuthenticationMiddleware"/>.
/// </summary>
/// <param name="next">The next item in the middleware pipeline.</param>
/// <param name="schemes">The <see cref="IAuthenticationSchemeProvider"/>.</param>
public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes)
{
if (next == null)
@ -27,8 +35,15 @@ namespace Microsoft.AspNetCore.Authentication
Schemes = schemes;
}
/// <summary>
/// Gets or sets the <see cref="IAuthenticationSchemeProvider"/>.
/// </summary>
public IAuthenticationSchemeProvider Schemes { get; set; }
/// <summary>
/// Invokes the middleware performing authentication.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/>.</param>
public async Task Invoke(HttpContext context)
{
context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
@ -61,4 +76,4 @@ namespace Microsoft.AspNetCore.Authentication
await _next(context);
}
}
}
}

View File

@ -13,6 +13,11 @@ namespace Microsoft.Extensions.DependencyInjection
/// </summary>
public static class AuthenticationServiceCollectionExtensions
{
/// <summary>
/// Registers services required by authentication services.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <returns>A <see cref="AuthenticationBuilder"/> that can be used to further configure authentication.</returns>
public static AuthenticationBuilder AddAuthentication(this IServiceCollection services)
{
if (services == null)
@ -27,10 +32,24 @@ namespace Microsoft.Extensions.DependencyInjection
return new AuthenticationBuilder(services);
}
/// <summary>
/// Registers services required by authentication services. <paramref name="defaultScheme"/> specifies the name of the
/// scheme to use by default when a specific scheme isn't requested.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="defaultScheme">The default scheme used as a fallback for all other schemes.</param>
/// <returns>A <see cref="AuthenticationBuilder"/> that can be used to further configure authentication.</returns>
public static AuthenticationBuilder AddAuthentication(this IServiceCollection services, string defaultScheme)
=> services.AddAuthentication(o => o.DefaultScheme = defaultScheme);
public static AuthenticationBuilder AddAuthentication(this IServiceCollection services, Action<AuthenticationOptions> configureOptions) {
/// <summary>
/// Registers services required by authentication services and configures <see cref="AuthenticationOptions"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="configureOptions">A delegate to configure <see cref="AuthenticationOptions"/>.</param>
/// <returns>A <see cref="AuthenticationBuilder"/> that can be used to further configure authentication.</returns>
public static AuthenticationBuilder AddAuthentication(this IServiceCollection services, Action<AuthenticationOptions> configureOptions)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
@ -61,6 +80,5 @@ namespace Microsoft.Extensions.DependencyInjection
options.SignInScheme ??= _authOptions.DefaultSignInScheme;
}
}
}
}

View File

@ -10,6 +10,12 @@ namespace Microsoft.AspNetCore.Authentication
/// </summary>
public class AccessDeniedContext : HandleRequestContext<RemoteAuthenticationOptions>
{
/// <summary>
/// Initializes a new instance of <see cref="AccessDeniedContext"/>.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/>.</param>
/// <param name="scheme">The <see cref="AuthenticationScheme"/>.</param>
/// <param name="options">The <see cref="RemoteAuthenticationOptions"/>.</param>
public AccessDeniedContext(
HttpContext context,
AuthenticationScheme scheme,

View File

@ -5,8 +5,18 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Authentication
{
/// <summary>
/// Base context type for handling authentication request.
/// </summary>
/// <typeparam name="TOptions"></typeparam>
public class HandleRequestContext<TOptions> : BaseContext<TOptions> where TOptions : AuthenticationSchemeOptions
{
/// <summary>
/// Initializes a new instance of <see cref="HandleRequestContext{TOptions}"/>.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/>.</param>
/// <param name="scheme">The <see cref="AuthenticationScheme"/>.</param>
/// <param name="options">The authentication scheme options.</param>
protected HandleRequestContext(
HttpContext context,
AuthenticationScheme scheme,

View File

@ -42,8 +42,16 @@ namespace Microsoft.AspNetCore.Authentication
/// </summary>
public void Success() => Result = HandleRequestResult.Success(new AuthenticationTicket(Principal!, Properties, Scheme.Name));
/// <summary>
/// Indicates that authentication failed.
/// </summary>
/// <param name="failure">The exception associated with the failure.</param>
public void Fail(Exception failure) => Result = HandleRequestResult.Fail(failure);
/// <summary>
/// Indicates that authentication failed.
/// </summary>
/// <param name="failureMessage">The exception associated with the failure.</param>
public void Fail(string failureMessage) => Result = HandleRequestResult.Fail(failureMessage);
}
}

View File

@ -6,11 +6,24 @@ using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Authentication
{
/// <summary>
/// Allows subscribing to events raised during remote authentication.
/// </summary>
public class RemoteAuthenticationEvents
{
/// <summary>
/// Invoked when an access denied error was returned by the remote server.
/// </summary>
public Func<AccessDeniedContext, Task> OnAccessDenied { get; set; } = context => Task.CompletedTask;
/// <summary>
/// Invoked when there is a remote failure.
/// </summary>
public Func<RemoteFailureContext, Task> OnRemoteFailure { get; set; } = context => Task.CompletedTask;
/// <summary>
/// Invoked after the remote ticket has been received.
/// </summary>
public Func<TicketReceivedContext, Task> OnTicketReceived { get; set; } = context => Task.CompletedTask;
/// <summary>
@ -28,4 +41,4 @@ namespace Microsoft.AspNetCore.Authentication
/// </summary>
public virtual Task TicketReceived(TicketReceivedContext context) => OnTicketReceived(context);
}
}
}

View File

@ -11,6 +11,13 @@ namespace Microsoft.AspNetCore.Authentication
/// </summary>
public class RemoteFailureContext : HandleRequestContext<RemoteAuthenticationOptions>
{
/// <summary>
/// Initializes a new instance of <see cref="RemoteFailureContext"/>.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/>.</param>
/// <param name="scheme">The <see cref="AuthenticationScheme"/>.</param>
/// <param name="options">The <see cref="RemoteAuthenticationOptions"/>.</param>
/// <param name="failure">User friendly error message for the error.</param>
public RemoteFailureContext(
HttpContext context,
AuthenticationScheme scheme,

View File

@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Authentication
private AuthenticationProperties? _properties;
/// <summary>
/// Constructor.
/// Initializes a new instance of <see cref="ResultContext{TOptions}"/>.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="scheme">The authentication scheme.</param>

View File

@ -1,7 +1,6 @@
// 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 Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Authentication
@ -11,6 +10,13 @@ namespace Microsoft.AspNetCore.Authentication
/// </summary>
public class TicketReceivedContext : RemoteAuthenticationContext<RemoteAuthenticationOptions>
{
/// <summary>
/// Initializes a new instance of <see cref="TicketReceivedContext"/>.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/>.</param>
/// <param name="scheme">The <see cref="AuthenticationScheme"/>.</param>
/// <param name="options">The <see cref="RemoteAuthenticationOptions"/>.</param>
/// <param name="ticket">The received ticket.</param>
public TicketReceivedContext(
HttpContext context,
AuthenticationScheme scheme,
@ -19,6 +25,9 @@ namespace Microsoft.AspNetCore.Authentication
: base(context, scheme, options, ticket?.Properties)
=> Principal = ticket?.Principal;
/// <summary>
/// Gets or sets the URL to redirect to after signin.
/// </summary>
public string? ReturnUri { get; set; }
}
}

View File

@ -93,6 +93,10 @@ namespace Microsoft.AspNetCore.Authentication
return new HandleRequestResult() { Skipped = true };
}
/// <summary>
/// Indicates that there were no results produced during authentication.
/// </summary>
/// <returns>The result.</returns>
public new static HandleRequestResult NoResult()
{
return new HandleRequestResult() { None = true };

View File

@ -5,10 +5,24 @@ using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNetCore.Authentication
{
/// <summary>
/// Contract for serialzing authentication data.
/// </summary>
/// <typeparam name="TModel">The type of the model being serialized.</typeparam>
public interface IDataSerializer<TModel>
{
/// <summary>
/// Serializes the specified <paramref name="model"/>.
/// </summary>
/// <param name="model">The value to serialize.</param>
/// <returns>The serialized data.</returns>
byte[] Serialize(TModel model);
/// <summary>
/// Deserializes the specified <paramref name="data"/> as an instance of type <typeparamref name="TModel"/>.
/// </summary>
/// <param name="data">The bytes being deserialized.</param>
/// <returns>The model.</returns>
[return: MaybeNull]
TModel Deserialize(byte[] data);
}

View File

@ -5,15 +5,41 @@ using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNetCore.Authentication
{
/// <summary>
/// A contract for securing data.
/// </summary>
/// <typeparam name="TData">The type of the data to protect.</typeparam>
public interface ISecureDataFormat<TData>
{
/// <summary>
/// Protects the specified <paramref name="data"/>.
/// </summary>
/// <param name="data">The value to protect</param>
/// <returns>The data protected value.</returns>
string Protect(TData data);
/// <summary>
/// Protects the specified <paramref name="data"/> for the specified <paramref name="purpose"/>.
/// </summary>
/// <param name="data">The value to protect</param>
/// <param name="purpose">The purpose.</param>
/// <returns>A data protected value.</returns>
string Protect(TData data, string? purpose);
/// <summary>
/// Unprotects the specified <paramref name="protectedText"/>.
/// </summary>
/// <param name="protectedText">The data protected value.</param>
/// <returns>An instance of <typeparamref name="TData"/>.</returns>
[return: MaybeNull]
TData Unprotect(string protectedText);
/// <summary>
/// Unprotects the specified <paramref name="protectedText"/> using the specified <paramref name="purpose"/>.
/// </summary>
/// <param name="protectedText">The data protected value.</param>
/// <param name="purpose">The purpose.</param>
/// <returns>An instance of <typeparamref name="TData"/>.</returns>
[return: MaybeNull]
TData Unprotect(string protectedText, string? purpose);
}

View File

@ -1,7 +1,6 @@
// 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

View File

@ -5,8 +5,17 @@ using System.Text.Json;
namespace Microsoft.AspNetCore.Authentication
{
/// <summary>
/// Authentication extensions to <see cref="JsonDocument"/>.
/// </summary>
public static class JsonDocumentAuthExtensions
{
/// <summary>
/// Gets a string property value from the specified <see cref="JsonElement"/>.
/// </summary>
/// <param name="element">The <see cref="JsonElement"/>.</param>
/// <param name="key">The property name.</param>
/// <returns>The property value.</returns>
public static string? GetString(this JsonElement element, string key)
{
if (element.TryGetProperty(key, out var property) && property.ValueKind != JsonValueKind.Null)

View File

@ -4,7 +4,7 @@
<Description>ASP.NET Core common types used by the various authentication middleware components.</Description>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<IsAspNetCoreApp>true</IsAspNetCoreApp>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<NoWarn>$(NoWarn.Replace('1591', ''))</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;authentication;security</PackageTags>
<IsPackable>false</IsPackable>

View File

@ -15,21 +15,33 @@ namespace Microsoft.AspNetCore.Authentication
/// </summary>
public class PolicySchemeHandler : SignInAuthenticationHandler<PolicySchemeOptions>
{
/// <summary>
/// Initializes a new instance of <see cref="PolicySchemeHandler"/>.
/// </summary>
/// <param name="options">The monitor for the options instance.</param>
/// <param name="logger">The <see cref="ILoggerFactory"/>.</param>
/// <param name="encoder">The <see cref="UrlEncoder"/>.</param>
/// <param name="clock">The <see cref="ISystemClock"/>.</param>
public PolicySchemeHandler(IOptionsMonitor<PolicySchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{ }
/// <inheritdoc />
protected override Task HandleChallengeAsync(AuthenticationProperties? properties)
=> throw new NotImplementedException();
/// <inheritdoc />
protected override Task HandleForbiddenAsync(AuthenticationProperties? properties)
=> throw new NotImplementedException();
/// <inheritdoc />
protected override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties)
=> throw new NotImplementedException();
/// <inheritdoc />
protected override Task HandleSignOutAsync(AuthenticationProperties? properties)
=> throw new NotImplementedException();
/// <inheritdoc />
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
=> throw new NotImplementedException();
}

View File

@ -5,8 +5,16 @@ using Microsoft.AspNetCore.DataProtection;
namespace Microsoft.AspNetCore.Authentication
{
/// <summary>
/// A <see cref="SecureDataFormat{TData}"/> instance to secure
/// <see cref="AuthenticationProperties"/>.
/// </summary>
public class PropertiesDataFormat : SecureDataFormat<AuthenticationProperties>
{
/// <summary>
/// Initializes a new instance of <see cref="PropertiesDataFormat"/>.
/// </summary>
/// <param name="protector">The <see cref="IDataProtector"/>.</param>
public PropertiesDataFormat(IDataProtector protector)
: base(new PropertiesSerializer(), protector)
{

View File

@ -7,12 +7,19 @@ using System.IO;
namespace Microsoft.AspNetCore.Authentication
{
/// <summary>
/// A <see cref="IDataSerializer{TModel}"/> for <see cref="AuthenticationProperties"/>.
/// </summary>
public class PropertiesSerializer : IDataSerializer<AuthenticationProperties>
{
private const int FormatVersion = 1;
/// <summary>
/// Gets the default instance of <see cref="PropertiesSerializer"/>.
/// </summary>
public static PropertiesSerializer Default { get; } = new PropertiesSerializer();
/// <inheritdoc />
public virtual byte[] Serialize(AuthenticationProperties model)
{
using (var memory = new MemoryStream())
@ -26,6 +33,7 @@ namespace Microsoft.AspNetCore.Authentication
}
}
/// <inheritdoc />
public virtual AuthenticationProperties? Deserialize(byte[] data)
{
using (var memory = new MemoryStream(data))
@ -37,6 +45,7 @@ namespace Microsoft.AspNetCore.Authentication
}
}
/// <inheritdoc />
public virtual void Write(BinaryWriter writer, AuthenticationProperties properties)
{
if (writer == null)
@ -59,6 +68,7 @@ namespace Microsoft.AspNetCore.Authentication
}
}
/// <inheritdoc />
public virtual AuthenticationProperties? Read(BinaryReader reader)
{
if (reader == null)

View File

@ -12,6 +12,12 @@ using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Authentication
{
/// <summary>
/// An opinionated abstraction for an <see cref="AuthenticationHandler{TOptions}"/> that performs authentication using a separately hosted
/// provider.
/// </summary>
/// <typeparam name="TOptions">The type for the options used to configure the authentication handler.</typeparam>
public abstract class RemoteAuthenticationHandler<TOptions> : AuthenticationHandler<TOptions>, IAuthenticationRequestHandler
where TOptions : RemoteAuthenticationOptions, new()
{
@ -19,6 +25,9 @@ namespace Microsoft.AspNetCore.Authentication
private const string CorrelationMarker = "N";
private const string AuthSchemeKey = ".AuthScheme";
/// <summary>
/// The authentication scheme used by default for signin.
/// </summary>
protected string? SignInScheme => Options.SignInScheme;
/// <summary>
@ -31,15 +40,32 @@ namespace Microsoft.AspNetCore.Authentication
set { base.Events = value; }
}
/// <summary>
/// Initializes a new instance of <see cref="RemoteAuthenticationHandler{TOptions}" />.
/// </summary>
/// <param name="options">The monitor for the options instance.</param>
/// <param name="logger">The <see cref="ILoggerFactory"/>.</param>
/// <param name="encoder">The <see cref="UrlEncoder"/>.</param>
/// <param name="clock">The <see cref="ISystemClock"/>.</param>
protected RemoteAuthenticationHandler(IOptionsMonitor<TOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock) { }
/// <inheritdoc />
protected override Task<object> CreateEventsAsync()
=> Task.FromResult<object>(new RemoteAuthenticationEvents());
/// <summary>
/// Gets a value that determines if the current authentication request should be handled by <see cref="HandleRequestAsync" />.
/// </summary>
/// <returns><see langword="true"/> to handle the operation, otherwise <see langword="false"/>.</returns>
public virtual Task<bool> ShouldHandleRequestAsync()
=> Task.FromResult(Options.CallbackPath == Request.Path);
/// <summary>
/// Handles the current authentication request.
/// </summary>
/// <returns><see langword="true"/> if authentication was handled, otherwise <see langword="false"/>.</returns>
public virtual async Task<bool> HandleRequestAsync()
{
if (!await ShouldHandleRequestAsync())
@ -156,6 +182,7 @@ namespace Microsoft.AspNetCore.Authentication
/// </summary>
protected abstract Task<HandleRequestResult> HandleRemoteAuthenticateAsync();
/// <inheritdoc />
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var result = await Context.AuthenticateAsync(SignInScheme);
@ -182,9 +209,14 @@ namespace Microsoft.AspNetCore.Authentication
return AuthenticateResult.Fail("Remote authentication does not directly support AuthenticateAsync");
}
/// <inheritdoc />
protected override Task HandleForbiddenAsync(AuthenticationProperties properties)
=> Context.ForbidAsync(SignInScheme);
/// <summary>
/// Produces a cookie containing a nonce used to correlate the current remote authentication request.
/// </summary>
/// <param name="properties"></param>
protected virtual void GenerateCorrelationId(AuthenticationProperties properties)
{
if (properties == null)
@ -205,6 +237,11 @@ namespace Microsoft.AspNetCore.Authentication
Response.Cookies.Append(cookieName, CorrelationMarker, cookieOptions);
}
/// <summary>
/// Validates that the current request correlates wit hthe
/// </summary>
/// <param name="properties"></param>
/// <returns></returns>
protected virtual bool ValidateCorrelationId(AuthenticationProperties properties)
{
if (properties == null)
@ -242,6 +279,11 @@ namespace Microsoft.AspNetCore.Authentication
return true;
}
/// <summary>
/// Derived types may override this method to handle access denied errors.
/// </summary>
/// <param name="properties">The <see cref="AuthenticationProperties"/>.</param>
/// <returns>The <see cref="HandleRequestResult"/>.</returns>
protected virtual async Task<HandleRequestResult> HandleAccessDeniedErrorAsync(AuthenticationProperties properties)
{
Logger.AccessDeniedError();

View File

@ -117,6 +117,9 @@ namespace Microsoft.AspNetCore.Authentication
/// </summary>
public TimeSpan RemoteAuthenticationTimeout { get; set; } = TimeSpan.FromMinutes(15);
/// <summary>
/// Gets or sets a value that allows subscribing to remote authentication events.
/// </summary>
public new RemoteAuthenticationEvents Events
{
get => (RemoteAuthenticationEvents)base.Events!;

View File

@ -16,6 +16,10 @@ namespace Microsoft.AspNetCore.Authentication
/// </summary>
protected virtual string? AdditionalPath { get; }
/// <summary>
/// Configures <see cref="CookieOptions.Path"/> if not explicitly configured.
/// </summary>
/// <inheritdoc />
public override CookieOptions Build(HttpContext context, DateTimeOffset expiresFrom)
{
// check if the user has overridden the default value of path. If so, use that instead of our default value.

View File

@ -6,22 +6,33 @@ using Microsoft.AspNetCore.DataProtection;
namespace Microsoft.AspNetCore.Authentication
{
/// <summary>
/// An implementation for <see cref="ISecureDataFormat{TData}"/>.
/// </summary>
/// <typeparam name="TData"></typeparam>
public class SecureDataFormat<TData> : ISecureDataFormat<TData>
{
private readonly IDataSerializer<TData> _serializer;
private readonly IDataProtector _protector;
/// <summary>
/// Initializes a new instance of <see cref="SecureDataFormat{TData}"/>.
/// </summary>
/// <param name="serializer">The <see cref="IDataSerializer{TModel}"/>.</param>
/// <param name="protector">The <see cref="IDataProtector"/>.</param>
public SecureDataFormat(IDataSerializer<TData> serializer, IDataProtector protector)
{
_serializer = serializer;
_protector = protector;
}
/// <inheritdoc />
public string Protect(TData data)
{
return Protect(data, purpose: null);
}
/// <inheritdoc />
public string Protect(TData data, string? purpose)
{
var userData = _serializer.Serialize(data);
@ -36,12 +47,14 @@ namespace Microsoft.AspNetCore.Authentication
return Base64UrlTextEncoder.Encode(protectedData);
}
/// <inheritdoc />
[return: MaybeNull]
public TData Unprotect(string protectedText)
{
return Unprotect(protectedText, purpose: null);
}
/// <inheritdoc />
[return: MaybeNull]
public TData Unprotect(string protectedText, string? purpose)
{

View File

@ -1,7 +1,6 @@
// 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.Text.Encodings.Web;
using System.Threading.Tasks;
@ -16,9 +15,17 @@ namespace Microsoft.AspNetCore.Authentication
public abstract class SignInAuthenticationHandler<TOptions> : SignOutAuthenticationHandler<TOptions>, IAuthenticationSignInHandler
where TOptions : AuthenticationSchemeOptions, new()
{
/// <summary>
/// Initializes a new instance of <see cref="SignInAuthenticationHandler{TOptions}"/>.
/// </summary>
/// <param name="options">The monitor for the options instance.</param>
/// <param name="logger">The <see cref="ILoggerFactory"/>.</param>
/// <param name="encoder">The <see cref="UrlEncoder"/>.</param>
/// <param name="clock">The <see cref="ISystemClock"/>.</param>
public SignInAuthenticationHandler(IOptionsMonitor<TOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{ }
/// <inheritdoc/>
public virtual Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties)
{
var target = ResolveTarget(Options.ForwardSignIn);

View File

@ -1,7 +1,6 @@
// 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.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@ -15,9 +14,17 @@ namespace Microsoft.AspNetCore.Authentication
public abstract class SignOutAuthenticationHandler<TOptions> : AuthenticationHandler<TOptions>, IAuthenticationSignOutHandler
where TOptions : AuthenticationSchemeOptions, new()
{
/// <summary>
/// Initializes a new instance of <see cref="SignOutAuthenticationHandler{TOptions}"/>.
/// </summary>
/// <param name="options">The monitor for the options instance.</param>
/// <param name="logger">The <see cref="ILoggerFactory"/>.</param>
/// <param name="encoder">The <see cref="UrlEncoder"/>.</param>
/// <param name="clock">The <see cref="ISystemClock"/>.</param>
public SignOutAuthenticationHandler(IOptionsMonitor<TOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{ }
/// <inheritdoc/>
public virtual Task SignOutAsync(AuthenticationProperties? properties)
{
var target = ResolveTarget(Options.ForwardSignOut);
@ -30,7 +37,6 @@ namespace Microsoft.AspNetCore.Authentication
/// Override this method to handle SignOut.
/// </summary>
/// <param name="properties"></param>
/// <returns>A Task.</returns>
protected abstract Task HandleSignOutAsync(AuthenticationProperties? properties);
}
}

View File

@ -3,6 +3,9 @@
namespace Microsoft.AspNetCore.Authentication
{
/// <summary>
/// Allows encoding and decoding base-64 url encoded text.
/// </summary>
public static class Base64UrlTextEncoder
{
/// <summary>

View File

@ -5,8 +5,16 @@ using Microsoft.AspNetCore.DataProtection;
namespace Microsoft.AspNetCore.Authentication
{
/// <summary>
/// A <see cref="SecureDataFormat{TData}"/> instance to secure
/// <see cref="AuthenticationTicket"/>.
/// </summary>
public class TicketDataFormat : SecureDataFormat<AuthenticationTicket>
{
/// <summary>
/// Initializes a new instance of <see cref="TicketDataFormat"/>.
/// </summary>
/// <param name="protector">The <see cref="IDataProtector"/>.</param>
public TicketDataFormat(IDataProtector protector)
: base(TicketSerializer.Default, protector)
{

View File

@ -9,13 +9,20 @@ using System.Security.Claims;
namespace Microsoft.AspNetCore.Authentication
{
// This MUST be kept in sync with Microsoft.Owin.Security.Interop.AspNetTicketSerializer
/// <summary>
/// Serializes and deserializes <see cref="AuthenticationTicket"/> instances.
/// </summary>
public class TicketSerializer : IDataSerializer<AuthenticationTicket>
{
private const string DefaultStringPlaceholder = "\0";
private const int FormatVersion = 5;
/// <summary>
/// Gets the default implementation for <see cref="TicketSerializer"/>.
/// </summary>
public static TicketSerializer Default { get; } = new TicketSerializer();
/// <inheritdoc/>
public virtual byte[] Serialize(AuthenticationTicket ticket)
{
using (var memory = new MemoryStream())
@ -28,6 +35,7 @@ namespace Microsoft.AspNetCore.Authentication
}
}
/// <inheritdoc/>
public virtual AuthenticationTicket? Deserialize(byte[] data)
{
using (var memory = new MemoryStream(data))
@ -39,6 +47,11 @@ namespace Microsoft.AspNetCore.Authentication
}
}
/// <summary>
/// Writes the <paramref name="ticket"/> using the specified <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The <see cref="BinaryWriter"/>.</param>
/// <param name="ticket">The <see cref="AuthenticationTicket"/>.</param>
public virtual void Write(BinaryWriter writer, AuthenticationTicket ticket)
{
if (writer == null)
@ -66,6 +79,11 @@ namespace Microsoft.AspNetCore.Authentication
PropertiesSerializer.Default.Write(writer, ticket.Properties);
}
/// <summary>
/// Writes the specified <paramref name="identity" />.
/// </summary>
/// <param name="writer">The <see cref="BinaryWriter" />.</param>
/// <param name="identity">The <see cref="ClaimsIdentity" />.</param>
protected virtual void WriteIdentity(BinaryWriter writer, ClaimsIdentity identity)
{
if (writer == null)
@ -114,6 +132,7 @@ namespace Microsoft.AspNetCore.Authentication
}
}
/// <inheritdoc/>
protected virtual void WriteClaim(BinaryWriter writer, Claim claim)
{
if (writer == null)
@ -142,6 +161,11 @@ namespace Microsoft.AspNetCore.Authentication
}
}
/// <summary>
/// Reads an <see cref="AuthenticationTicket"/>.
/// </summary>
/// <param name="reader">The <see cref="BinaryReader"/>.</param>
/// <returns>The <see cref="AuthenticationTicket"/> if the format is supported, otherwise <see langword="null"/>.</returns>
public virtual AuthenticationTicket? Read(BinaryReader reader)
{
if (reader == null)
@ -175,6 +199,11 @@ namespace Microsoft.AspNetCore.Authentication
return new AuthenticationTicket(new ClaimsPrincipal(identities), properties, scheme);
}
/// <summary>
/// Reads a <see cref="ClaimsIdentity"/> from a <see cref="BinaryReader"/>.
/// </summary>
/// <param name="reader">The <see cref="BinaryReader"/>.</param>
/// <returns>The read <see cref="ClaimsIdentity"/>.</returns>
protected virtual ClaimsIdentity ReadIdentity(BinaryReader reader)
{
if (reader == null)
@ -216,6 +245,12 @@ namespace Microsoft.AspNetCore.Authentication
return identity;
}
/// <summary>
/// Reads a <see cref="Claim"/> and adds it to the specified <paramref name="identity"/>.
/// </summary>
/// <param name="reader">The <see cref="BinaryReader"/>.</param>
/// <param name="identity">The <see cref="ClaimsIdentity"/> to add the claim to.</param>
/// <returns>The read <see cref="Claim"/>.</returns>
protected virtual Claim ReadClaim(BinaryReader reader, ClaimsIdentity identity)
{
if (reader == null)

View File

@ -6,14 +6,24 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Authentication.JwtBearer
{
/// <summary>
/// A <see cref="ResultContext{TOptions}"/> when authentication has failed.
/// </summary>
public class AuthenticationFailedContext : ResultContext<JwtBearerOptions>
{
/// <summary>
/// Initializes a new instance of <see cref="AuthenticationFailedContext"/>.
/// </summary>
/// <inheritdoc />
public AuthenticationFailedContext(
HttpContext context,
AuthenticationScheme scheme,
JwtBearerOptions options)
: base(context, scheme, options) { }
/// <summary>
/// Gets or sets the exception associated with the authentication failure.
/// </summary>
public Exception Exception { get; set; }
}
}
}

View File

@ -6,8 +6,15 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Authentication.JwtBearer
{
/// <summary>
/// A <see cref="ResultContext{TOptions}"/> when access to a resource is forbidden.
/// </summary>
public class ForbiddenContext : ResultContext<JwtBearerOptions>
{
/// <summary>
/// Initializes a new instance of <see cref="ForbiddenContext"/>.
/// </summary>
/// <inheritdoc />
public ForbiddenContext(
HttpContext context,
AuthenticationScheme scheme,

View File

@ -6,8 +6,15 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Authentication.JwtBearer
{
/// <summary>
/// A <see cref="PropertiesContext{TOptions}"/> when access to a resource authenticated using JWT bearer is challenged.
/// </summary>
public class JwtBearerChallengeContext : PropertiesContext<JwtBearerOptions>
{
/// <summary>
/// Initializes a new instance of <see cref="JwtBearerChallengeContext"/>.
/// </summary>
/// <inheritdoc />
public JwtBearerChallengeContext(
HttpContext context,
AuthenticationScheme scheme,

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
public Func<AuthenticationFailedContext, Task> OnAuthenticationFailed { get; set; } = context => Task.CompletedTask;
/// <summary>
/// Invoked if Authorization fails and results in a Forbidden response
/// Invoked if Authorization fails and results in a Forbidden response.
/// </summary>
public Func<ForbiddenContext, Task> OnForbidden { get; set; } = context => Task.CompletedTask;
@ -36,14 +36,29 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
/// </summary>
public Func<JwtBearerChallengeContext, Task> OnChallenge { get; set; } = context => Task.CompletedTask;
/// <summary>
/// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed.
/// </summary>
public virtual Task AuthenticationFailed(AuthenticationFailedContext context) => OnAuthenticationFailed(context);
/// <summary>
/// Invoked if Authorization fails and results in a Forbidden response
/// </summary>
public virtual Task Forbidden(ForbiddenContext context) => OnForbidden(context);
/// <summary>
/// Invoked when a protocol message is first received.
/// </summary>
public virtual Task MessageReceived(MessageReceivedContext context) => OnMessageReceived(context);
/// <summary>
/// Invoked after the security token has passed validation and a ClaimsIdentity has been generated.
/// </summary>
public virtual Task TokenValidated(TokenValidatedContext context) => OnTokenValidated(context);
/// <summary>
/// Invoked before a challenge is sent back to the caller.
/// </summary>
public virtual Task Challenge(JwtBearerChallengeContext context) => OnChallenge(context);
}
}

View File

@ -9,17 +9,58 @@ using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Extension methods to configure JWT bearer authentication.
/// </summary>
public static class JwtBearerExtensions
{
/// <summary>
/// Enables JWT-bearer authentication using the default scheme <see cref="JwtBearerDefaults.AuthenticationScheme"/>.
/// <para>
/// JWT bearer authentication performs authentication by extracting and validating a JWT token from the <c>Authorization</c> request header.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <returns>A reference to <paramref name="builder"/> after the operation has completed.</returns>
public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder)
=> builder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, _ => { });
/// <summary>
/// Enables JWT-bearer authentication using the default scheme <see cref="JwtBearerDefaults.AuthenticationScheme"/>.
/// <para>
/// JWT bearer authentication performs authentication by extracting and validating a JWT token from the <c>Authorization</c> request header.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="configureOptions">A delegate that allows configuring <see cref="JwtBearerOptions"/>.</param>
/// <returns>A reference to <paramref name="builder"/> after the operation has completed.</returns>
public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, Action<JwtBearerOptions> configureOptions)
=> builder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, configureOptions);
/// <summary>
/// Enables JWT-bearer authentication using the specified scheme.
/// <para>
/// JWT bearer authentication performs authentication by extracting and validating a JWT token from the <c>Authorization</c> request header.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="authenticationScheme">The authentication scheme.</param>
/// <param name="configureOptions">A delegate that allows configuring <see cref="JwtBearerOptions"/>.</param>
/// <returns>A reference to <paramref name="builder"/> after the operation has completed.</returns>
public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, Action<JwtBearerOptions> configureOptions)
=> builder.AddJwtBearer(authenticationScheme, displayName: null, configureOptions: configureOptions);
/// <summary>
/// Enables JWT-bearer authentication using the specified scheme.
/// <para>
/// JWT bearer authentication performs authentication by extracting and validating a JWT token from the <c>Authorization</c> request header.
/// </para>
/// </summary>
/// <param name="builder">The <see cref="AuthenticationBuilder"/>.</param>
/// <param name="authenticationScheme">The authentication scheme.</param>
/// <param name="displayName">The display name for the authentication handler.</param>
/// <param name="configureOptions">A delegate that allows configuring <see cref="JwtBearerOptions"/>.</param>
/// <returns>A reference to <paramref name="builder"/> after the operation has completed.</returns>
public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<JwtBearerOptions> configureOptions)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerPostConfigureOptions>());

View File

@ -9,7 +9,6 @@ using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@ -19,10 +18,17 @@ using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Authentication.JwtBearer
{
/// <summary>
/// An <see cref="AuthenticationHandler{TOptions}"/> that can perform JWT-bearer based authentication.
/// </summary>
public class JwtBearerHandler : AuthenticationHandler<JwtBearerOptions>
{
private OpenIdConnectConfiguration _configuration;
/// <summary>
/// Initializes a new instance of <see cref="JwtBearerHandler"/>.
/// </summary>
/// <inheritdoc />
public JwtBearerHandler(IOptionsMonitor<JwtBearerOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{ }
@ -37,6 +43,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
set => base.Events = value;
}
/// <inheritdoc />
protected override Task<object> CreateEventsAsync() => Task.FromResult<object>(new JwtBearerEvents());
/// <summary>
@ -192,6 +199,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
}
}
/// <inheritdoc />
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
var authResult = await HandleAuthenticateOnceSafeAsync();
@ -265,6 +273,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
}
}
/// <inheritdoc />
protected override Task HandleForbiddenAsync(AuthenticationProperties properties)
{
var forbiddenContext = new ForbiddenContext(Context, Scheme, Options);

View File

@ -17,7 +17,10 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
public class JwtBearerOptions : AuthenticationSchemeOptions
{
private JwtSecurityTokenHandler _defaultHandler = new JwtSecurityTokenHandler();
/// <summary>
/// Initializes a new instance of <see cref="JwtBearerOptions"/>.
/// </summary>
public JwtBearerOptions()
{
SecurityTokenValidators = new List<ISecurityTokenValidator> { _defaultHandler };

View File

@ -5,8 +5,15 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Authentication.JwtBearer
{
/// <summary>
/// A context for <see cref="JwtBearerEvents.OnMessageReceived"/>.
/// </summary>
public class MessageReceivedContext : ResultContext<JwtBearerOptions>
{
/// <summary>
/// Initializes a new instance of <see cref="MessageReceivedContext"/>.
/// </summary>
/// <inheritdoc />
public MessageReceivedContext(
HttpContext context,
AuthenticationScheme scheme,
@ -18,4 +25,4 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer
/// </summary>
public string Token { get; set; }
}
}
}

View File

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>ASP.NET Core middleware that enables an application to receive an OpenID Connect bearer token.</Description>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<NoWarn>$(NoWarn.Replace('1591', ''))</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;authentication;security</PackageTags>
</PropertyGroup>

View File

@ -6,14 +6,24 @@ using Microsoft.IdentityModel.Tokens;
namespace Microsoft.AspNetCore.Authentication.JwtBearer
{
/// <summary>
/// A context for <see cref="JwtBearerEvents.OnTokenValidated"/>.
/// </summary>
public class TokenValidatedContext : ResultContext<JwtBearerOptions>
{
/// <summary>
/// Initializes a new instance of <see cref="TokenValidatedContext"/>.
/// </summary>
/// <inheritdoc />
public TokenValidatedContext(
HttpContext context,
AuthenticationScheme scheme,
JwtBearerOptions options)
: base(context, scheme, options) { }
/// <summary>
/// Gets or sets the validated security token.
/// </summary>
public SecurityToken SecurityToken { get; set; }
}
}

View File

@ -40,6 +40,9 @@ namespace Microsoft.AspNetCore.Internal
private const string ChunkKeySuffix = "C";
private const string ChunkCountPrefix = "chunks-";
/// <summary>
/// Initializes a new instance of <see cref="ChunkingCookieManager"/>.
/// </summary>
public ChunkingCookieManager()
{
// Lowest common denominator. Safari has the lowest known limit (4093), and we leave little extra just in case.