#39 - Port the OAuth Bearer middleware from Katana.

This commit is contained in:
Chris Ross 2014-10-07 12:42:42 -07:00
parent 3426034bcb
commit 4853554147
17 changed files with 703 additions and 4 deletions

View File

@ -21,7 +21,7 @@
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>50113</DevelopmentServerPort>
<DevelopmentServerPort>12345</DevelopmentServerPort>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -28,7 +28,6 @@ namespace CookieSample
{
services.ConfigureOptions<ExternalAuthenticationOptions>(options =>
{
options.SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType;
});
});

View File

@ -7,6 +7,7 @@
"Microsoft.AspNet.Security.Google": "1.0.0-*",
"Microsoft.AspNet.Security.MicrosoftAccount": "1.0.0-*",
"Microsoft.AspNet.Security.Twitter": "1.0.0-*",
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
"Microsoft.Framework.OptionsModel": "1.0.0-*"

View File

@ -0,0 +1,114 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Security.Notifications;
namespace Microsoft.AspNet.Security.OAuth
{
/// <summary>
/// Base class used for certain event contexts
/// </summary>
public abstract class BaseValidatingContext<TOptions> : BaseContext<TOptions>
{
/// <summary>
/// Initializes base class used for certain event contexts
/// </summary>
protected BaseValidatingContext(
HttpContext context,
TOptions options)
: base(context, options)
{
}
/// <summary>
/// True if application code has called any of the Validate methods on this context.
/// </summary>
public bool IsValidated { get; private set; }
/// <summary>
/// True if application code has called any of the SetError methods on this context.
/// </summary>
public bool HasError { get; private set; }
/// <summary>
/// The error argument provided when SetError was called on this context. This is eventually
/// returned to the client app as the OAuth "error" parameter.
/// </summary>
public string Error { get; private set; }
/// <summary>
/// The optional errorDescription argument provided when SetError was called on this context. This is eventually
/// returned to the client app as the OAuth "error_description" parameter.
/// </summary>
public string ErrorDescription { get; private set; }
/// <summary>
/// The optional errorUri argument provided when SetError was called on this context. This is eventually
/// returned to the client app as the OAuth "error_uri" parameter.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "error_uri is a string value in the protocol")]
public string ErrorUri { get; private set; }
/// <summary>
/// Marks this context as validated by the application. IsValidated becomes true and HasError becomes false as a result of calling.
/// </summary>
/// <returns>True if the validation has taken effect.</returns>
public virtual bool Validated()
{
IsValidated = true;
HasError = false;
return true;
}
/// <summary>
/// Marks this context as not validated by the application. IsValidated and HasError become false as a result of calling.
/// </summary>
public virtual void Rejected()
{
IsValidated = false;
HasError = false;
}
/// <summary>
/// Marks this context as not validated by the application and assigns various error information properties.
/// HasError becomes true and IsValidated becomes false as a result of calling.
/// </summary>
/// <param name="error">Assigned to the Error property</param>
public void SetError(string error)
{
SetError(error, null);
}
/// <summary>
/// Marks this context as not validated by the application and assigns various error information properties.
/// HasError becomes true and IsValidated becomes false as a result of calling.
/// </summary>
/// <param name="error">Assigned to the Error property</param>
/// <param name="errorDescription">Assigned to the ErrorDescription property</param>
public void SetError(string error,
string errorDescription)
{
SetError(error, errorDescription, null);
}
/// <summary>
/// Marks this context as not validated by the application and assigns various error information properties.
/// HasError becomes true and IsValidated becomes false as a result of calling.
/// </summary>
/// <param name="error">Assigned to the Error property</param>
/// <param name="errorDescription">Assigned to the ErrorDescription property</param>
/// <param name="errorUri">Assigned to the ErrorUri property</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#", Justification = "error_uri is a string value in the protocol")]
public void SetError(string error,
string errorDescription,
string errorUri)
{
Error = error;
ErrorDescription = errorDescription;
ErrorUri = errorUri;
Rejected();
HasError = true;
}
}
}

View File

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Security.Claims;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Security;
namespace Microsoft.AspNet.Security.OAuth
{
/// <summary>
/// Base class used for certain event contexts
/// </summary>
public abstract class BaseValidatingTicketContext<TOptions> : BaseValidatingContext<TOptions>
{
/// <summary>
/// Initializes base class used for certain event contexts
/// </summary>
protected BaseValidatingTicketContext(
HttpContext context,
TOptions options,
AuthenticationTicket ticket)
: base(context, options)
{
Ticket = ticket;
}
/// <summary>
/// Contains the identity and properties for the application to authenticate. If the Validated method
/// is invoked with an AuthenticationTicket or ClaimsIdentity argument, that new value is assigned to
/// this property in addition to changing IsValidated to true.
/// </summary>
public AuthenticationTicket Ticket { get; private set; }
/// <summary>
/// Replaces the ticket information on this context and marks it as as validated by the application.
/// IsValidated becomes true and HasError becomes false as a result of calling.
/// </summary>
/// <param name="ticket">Assigned to the Ticket property</param>
/// <returns>True if the validation has taken effect.</returns>
public bool Validated(AuthenticationTicket ticket)
{
Ticket = ticket;
return Validated();
}
/// <summary>
/// Alters the ticket information on this context and marks it as as validated by the application.
/// IsValidated becomes true and HasError becomes false as a result of calling.
/// </summary>
/// <param name="identity">Assigned to the Ticket.Identity property</param>
/// <returns>True if the validation has taken effect.</returns>
public bool Validated(ClaimsIdentity identity)
{
AuthenticationProperties properties = Ticket != null ? Ticket.Properties : new AuthenticationProperties();
return Validated(new AuthenticationTicket(identity, properties));
}
}
}

View File

@ -0,0 +1,37 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
namespace Microsoft.AspNet.Security.OAuth
{
/// <summary>
/// Specifies callback methods which the <see cref="OAuthBearerAuthenticationMiddleware"></see> invokes to enable developer control over the authentication process. />
/// </summary>
public interface IOAuthBearerAuthenticationNotifications
{
/// <summary>
/// Invoked before the <see cref="System.Security.Claims.ClaimsIdentity"/> is created. Gives the application an
/// opportunity to find the identity from a different location, adjust, or reject the token.
/// </summary>
/// <param name="context">Contains the token string.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
Task RequestToken(OAuthRequestTokenContext context);
/// <summary>
/// Called each time a request identity has been validated by the middleware. By implementing this method the
/// application may alter or reject the identity which has arrived with the request.
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
Task ValidateIdentity(OAuthValidateIdentityContext context);
/// <summary>
/// Called each time a challenge is being sent to the client. By implementing this method the application
/// may modify the challenge as needed.
/// </summary>
/// <param name="context">Contains the default challenge.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
Task ApplyChallenge(OAuthChallengeContext context);
}
}

View File

@ -3,7 +3,6 @@
using System;
using System.Globalization;
using System.Net.Http;
using System.Security.Claims;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Security;

View File

@ -0,0 +1,73 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Security.OAuth
{
/// <summary>
/// OAuth bearer token middleware provider
/// </summary>
public class OAuthBearerAuthenticationNotifications : IOAuthBearerAuthenticationNotifications
{
/// <summary>
/// Initializes a new instance of the <see cref="OAuthBearerAuthenticationProvider"/> class
/// </summary>
public OAuthBearerAuthenticationNotifications()
{
OnRequestToken = context => Task.FromResult<object>(null);
OnValidateIdentity = context => Task.FromResult<object>(null);
OnApplyChallenge = context =>
{
context.HttpContext.Response.Headers.AppendValues("WWW-Authenticate", context.Challenge);
return Task.FromResult(0);
};
}
/// <summary>
/// Handles processing OAuth bearer token.
/// </summary>
public Func<OAuthRequestTokenContext, Task> OnRequestToken { get; set; }
/// <summary>
/// Handles validating the identity produced from an OAuth bearer token.
/// </summary>
public Func<OAuthValidateIdentityContext, Task> OnValidateIdentity { get; set; }
/// <summary>
/// Handles applying the authentication challenge to the response message.
/// </summary>
public Func<OAuthChallengeContext, Task> OnApplyChallenge { get; set; }
/// <summary>
/// Handles processing OAuth bearer token.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public virtual Task RequestToken(OAuthRequestTokenContext context)
{
return OnRequestToken(context);
}
/// <summary>
/// Handles validating the identity produced from an OAuth bearer token.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public virtual Task ValidateIdentity(OAuthValidateIdentityContext context)
{
return OnValidateIdentity.Invoke(context);
}
/// <summary>
/// Handles applying the authentication challenge to the response message.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public Task ApplyChallenge(OAuthChallengeContext context)
{
return OnApplyChallenge(context);
}
}
}

View File

@ -0,0 +1,32 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Security.Notifications;
namespace Microsoft.AspNet.Security.OAuth
{
/// <summary>
/// Specifies the HTTP response header for the bearer authentication scheme.
/// </summary>
public class OAuthChallengeContext : BaseContext
{
/// <summary>
/// Initializes a new <see cref="OAuthRequestTokenContext"/>
/// </summary>
/// <param name="context">HTTP environment</param>
/// <param name="challenge">The www-authenticate header value.</param>
public OAuthChallengeContext(
HttpContext context,
string challenge)
: base(context)
{
Challenge = challenge;
}
/// <summary>
/// The www-authenticate header value.
/// </summary>
public string Challenge { get; protected set; }
}
}

View File

@ -8,7 +8,6 @@ using System.Security.Claims;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Security;
using Microsoft.AspNet.Security.Notifications;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.Security.OAuth
{

View File

@ -0,0 +1,32 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Security.Notifications;
namespace Microsoft.AspNet.Security.OAuth
{
/// <summary>
/// Specifies the HTTP request header for the bearer authentication scheme.
/// </summary>
public class OAuthRequestTokenContext : BaseContext
{
/// <summary>
/// Initializes a new <see cref="OAuthRequestTokenContext"/>
/// </summary>
/// <param name="context">HTTP environment</param>
/// <param name="token">The authorization header value.</param>
public OAuthRequestTokenContext(
HttpContext context,
string token)
: base(context)
{
Token = token;
}
/// <summary>
/// The authorization header value
/// </summary>
public string Token { get; set; }
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Security.OAuth
{
/// <summary>
/// Contains the authentication ticket data from an OAuth bearer token.
/// </summary>
public class OAuthValidateIdentityContext : BaseValidatingTicketContext<OAuthBearerAuthenticationOptions>
{
/// <summary>
/// Initializes a new instance of the <see cref="OAuthValidateIdentityContext"/> class
/// </summary>
/// <param name="context"></param>
/// <param name="options"></param>
/// <param name="ticket"></param>
public OAuthValidateIdentityContext(
HttpContext context,
OAuthBearerAuthenticationOptions options,
AuthenticationTicket ticket) : base(context, options, ticket)
{
}
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Security.OAuth
{
/// <summary>
/// Default values used by authorization server and bearer authentication.
/// </summary>
public static class OAuthBearerAuthenticationDefaults
{
/// <summary>
/// Default value for AuthenticationType property in the OAuthBearerAuthenticationOptions and
/// OAuthAuthorizationServerOptions.
/// </summary>
public const string AuthenticationType = "Bearer";
}
}

View File

@ -0,0 +1,41 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Security.OAuth;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Builder
{
/// <summary>
/// Extension methods to add OAuth Bearer authentication capabilities to an HTTP application pipeline
/// </summary>
public static class OAuthBearerAuthenticationExtensions
{
public static IServiceCollection ConfigureOAuthBearerAuthentication([NotNull] this IServiceCollection services, [NotNull] Action<OAuthBearerAuthenticationOptions> configure)
{
return services.ConfigureOptions(configure);
}
/// <summary>
/// Adds Bearer token processing to an HTTP application pipeline. This middleware understands appropriately
/// formatted and secured tokens which appear in the request header. If the Options.AuthenticationMode is Active, the
/// claims within the bearer token are added to the current request's IPrincipal User. If the Options.AuthenticationMode
/// is Passive, then the current request is not modified, but IAuthenticationManager AuthenticateAsync may be used at
/// any time to obtain the claims from the request's bearer token.
/// See also http://tools.ietf.org/html/rfc6749
/// </summary>
/// <param name="app">The application builder</param>
/// <param name="options">Options which control the processing of the bearer header.</param>
/// <returns>The application builder</returns>
public static IApplicationBuilder UseOAuthBearerAuthentication([NotNull] this IApplicationBuilder app, Action<OAuthBearerAuthenticationOptions> configureOptions = null, string optionsName = "")
{
return app.UseMiddleware<OAuthBearerAuthenticationMiddleware>(
new OptionsAction<OAuthBearerAuthenticationOptions>(configureOptions ?? (o => { }))
{
Name = optionsName
});
}
}
}

View File

@ -0,0 +1,126 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Security.Infrastructure;
using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Security.OAuth
{
internal class OAuthBearerAuthenticationHandler : AuthenticationHandler<OAuthBearerAuthenticationOptions>
{
private readonly ILogger _logger;
private readonly string _challenge;
public OAuthBearerAuthenticationHandler(ILogger logger, string challenge)
{
_logger = logger;
_challenge = challenge;
}
protected override AuthenticationTicket AuthenticateCore()
{
return AuthenticateCoreAsync().Result;
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
try
{
// Find token in default location
string requestToken = null;
string authorization = Request.Headers.Get("Authorization");
if (!string.IsNullOrEmpty(authorization))
{
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
requestToken = authorization.Substring("Bearer ".Length).Trim();
}
}
// Give application opportunity to find from a different location, adjust, or reject token
var requestTokenContext = new OAuthRequestTokenContext(Context, requestToken);
await Options.Notifications.RequestToken(requestTokenContext);
// If no token found, no further work possible
if (string.IsNullOrEmpty(requestTokenContext.Token))
{
return null;
}
// Call provider to process the token into data
var tokenReceiveContext = new AuthenticationTokenReceiveContext(
Context,
Options.AccessTokenFormat,
requestTokenContext.Token);
await Options.AccessTokenProvider.ReceiveAsync(tokenReceiveContext);
if (tokenReceiveContext.Ticket == null)
{
tokenReceiveContext.DeserializeTicket(tokenReceiveContext.Token);
}
AuthenticationTicket ticket = tokenReceiveContext.Ticket;
if (ticket == null)
{
_logger.WriteWarning("invalid bearer token received");
return null;
}
// Validate expiration time if present
DateTimeOffset currentUtc = Options.SystemClock.UtcNow;
if (ticket.Properties.ExpiresUtc.HasValue &&
ticket.Properties.ExpiresUtc.Value < currentUtc)
{
_logger.WriteWarning("expired bearer token received");
return null;
}
// Give application final opportunity to override results
var context = new OAuthValidateIdentityContext(Context, Options, ticket);
if (ticket != null &&
ticket.Identity != null &&
ticket.Identity.IsAuthenticated)
{
// bearer token with identity starts validated
context.Validated();
}
await Options.Notifications.ValidateIdentity(context);
if (!context.IsValidated)
{
return null;
}
// resulting identity values go back to caller
return context.Ticket;
}
catch (Exception ex)
{
_logger.WriteError("Authentication failed", ex);
return null;
}
}
protected override void ApplyResponseChallenge()
{
if (Response.StatusCode != 401)
{
return;
}
if (ChallengeContext != null)
{
OAuthChallengeContext challengeContext = new OAuthChallengeContext(Context, _challenge);
Options.Notifications.ApplyChallenge(challengeContext);
}
}
protected override void ApplyResponseGrant()
{
// N/A
}
}
}

View File

@ -0,0 +1,79 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Security.DataHandler;
using Microsoft.AspNet.Security.DataProtection;
using Microsoft.AspNet.Security.Infrastructure;
using Microsoft.Framework.Logging;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Security.OAuth
{
/// <summary>
/// Bearer authentication middleware component which is added to an HTTP pipeline. This class is not
/// created by application code directly, instead it is added by calling the the IAppBuilder UseOAuthBearerAuthentication
/// extension method.
/// </summary>
public class OAuthBearerAuthenticationMiddleware : AuthenticationMiddleware<OAuthBearerAuthenticationOptions>
{
private readonly ILogger _logger;
private readonly string _challenge;
/// <summary>
/// Bearer authentication component which is added to an HTTP pipeline. This constructor is not
/// called by application code directly, instead it is added by calling the the IAppBuilder UseOAuthBearerAuthentication
/// extension method.
/// </summary>
public OAuthBearerAuthenticationMiddleware(
RequestDelegate next,
IDataProtectionProvider dataProtectionProvider,
ILoggerFactory loggerFactory,
IOptionsAccessor<OAuthBearerAuthenticationOptions> options,
IOptionsAction<OAuthBearerAuthenticationOptions> configureOptions)
: base(next, options, configureOptions)
{
_logger = loggerFactory.Create<OAuthBearerAuthenticationMiddleware>();
if (!string.IsNullOrWhiteSpace(Options.Challenge))
{
_challenge = Options.Challenge;
}
else if (string.IsNullOrWhiteSpace(Options.Realm))
{
_challenge = "Bearer";
}
else
{
_challenge = "Bearer realm=\"" + Options.Realm + "\"";
}
if (Options.Notifications == null)
{
Options.Notifications = new OAuthBearerAuthenticationNotifications();
}
if (Options.AccessTokenFormat == null)
{
var dataProtector = DataProtectionHelpers.CreateDataProtector(dataProtectionProvider,
this.GetType().FullName, Options.AuthenticationType, "v1");
Options.AccessTokenFormat = new TicketDataFormat(dataProtector);
}
if (Options.AccessTokenProvider == null)
{
Options.AccessTokenProvider = new AuthenticationTokenProvider();
}
}
/// <summary>
/// Called by the AuthenticationMiddleware base class to create a per-request handler.
/// </summary>
/// <returns>A new instance of the request handler</returns>
protected override AuthenticationHandler<OAuthBearerAuthenticationOptions> CreateHandler()
{
return new OAuthBearerAuthenticationHandler(_logger, _challenge);
}
}
}

View File

@ -0,0 +1,66 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Security.Infrastructure;
namespace Microsoft.AspNet.Security.OAuth
{
/// <summary>
/// Options class provides information needed to control Bearer Authentication middleware behavior
/// </summary>
public class OAuthBearerAuthenticationOptions : AuthenticationOptions
{
/// <summary>
/// Creates an instance of bearer authentication options with default values.
/// </summary>
public OAuthBearerAuthenticationOptions() : base()
{
SystemClock = new SystemClock();
AuthenticationType = OAuthBearerAuthenticationDefaults.AuthenticationType;
}
/// <summary>
/// Determines what realm value is included when the bearer middleware adds a response header to an unauthorized request.
/// If not assigned, the response header does not have a realm.
/// </summary>
public string Realm { get; set; }
/// <summary>
/// Specifies the full challenge to send to the client, and should start with "Bearer". If a challenge is provided then the
/// Realm property is ignored. If no challenge is specified then one is created using "Bearer" and the value of the Realm
/// property.
/// </summary>
public string Challenge { get; set; }
/// <summary>
/// The object provided by the application to process events raised by the bearer authentication middleware.
/// The application may implement the interface fully, or it may create an instance of OAuthBearerAuthenticationProvider
/// and assign delegates only to the events it wants to process.
/// </summary>
public IOAuthBearerAuthenticationNotifications Notifications { get; set; }
/// <summary>
/// The data format used to un-protect the information contained in the access token.
/// If not provided by the application the default data protection provider depends on the host server.
/// The SystemWeb host on IIS will use ASP.NET machine key data protection, and HttpListener and other self-hosted
/// servers will use DPAPI data protection. If a different access token
/// provider or format is assigned, a compatible instance must be assigned to the OAuthAuthorizationServerOptions.AccessTokenProvider
/// and OAuthAuthorizationServerOptions.AccessTokenFormat of the authorization server.
/// </summary>
public ISecureDataFormat<AuthenticationTicket> AccessTokenFormat { get; set; }
/// <summary>
/// Receives the bearer token the client application will be providing to web application. If not provided the token
/// produced on the server's default data protection by using the AccessTokenFormat. If a different access token
/// provider or format is assigned, a compatible instance must be assigned to the OAuthAuthorizationServerOptions.AccessTokenProvider
/// and OAuthAuthorizationServerOptions.AccessTokenFormat of the authorization server.
/// </summary>
public IAuthenticationTokenProvider AccessTokenProvider { get; set; }
/// <summary>
/// Used to know what the current clock time is when calculating or validating token expiration. When not assigned default is based on
/// DateTimeOffset.UtcNow. This is typically needed only for unit testing.
/// </summary>
public ISystemClock SystemClock { get; set; }
}
}