Browse Source

Add non-GUID/string primary keys support for OpenIddictToken and add new log messages

pull/132/head
Kévin Chalet 10 years ago
parent
commit
3204c31560
  1. 2
      samples/Mvc.Server/Startup.cs
  2. 16
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs
  3. 18
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs
  4. 36
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  5. 26
      src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
  6. 37
      src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs
  7. 13
      src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs
  8. 2
      src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs
  9. 42
      src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs
  10. 31
      src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs

2
samples/Mvc.Server/Startup.cs

@ -142,7 +142,7 @@ namespace Mvc.Server {
// context.Applications.Add(new OpenIddictApplication {
// Id = "resource_server",
// DisplayName = "Main resource server",
// Secret = "875sqd4s5d748z78z7ds1ff8zz8814ff88ed8ea4z4zzd",
// Secret = Crypto.HashPassword("secret_secret_secret"),
// Type = OpenIddictConstants.ClientTypes.Confidential
// });

16
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs

@ -5,11 +5,13 @@
*/
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace OpenIddict.Infrastructure {
public partial class OpenIddictProvider<TUser, TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
@ -88,24 +90,24 @@ namespace OpenIddict.Infrastructure {
// If the received token is not a refresh token, set Revoked
// to false to indicate that the token cannot be revoked.
if (!context.Ticket.IsRefreshToken()) {
services.Logger.LogError("The revocation request was rejected because the token was not a refresh token.");
context.Revoked = false;
return;
}
// Extract the token identifier from the authentication ticket.
// If the identifier cannot be extracted, abort the revocation.
var identifier = context.Ticket.GetTicketId();
if (string.IsNullOrEmpty(identifier)) {
context.Revoked = true;
return;
}
Debug.Assert(!string.IsNullOrEmpty(identifier),
"The refresh token should contain a ticket identifier.");
// Retrieve the token from the database. If the token cannot be found,
// assume it is invalid and consider the revocation as successful.
var token = await services.Tokens.FindByIdAsync(identifier);
if (token == null) {
services.Logger.LogInformation("The refresh token '{Identifier}' was already revoked.", identifier);
context.Revoked = true;
return;
@ -114,6 +116,8 @@ namespace OpenIddict.Infrastructure {
// Revoke the refresh token.
await services.Tokens.RevokeAsync(token);
services.Logger.LogInformation("The refresh token '{Identifier}' was revoked.", identifier);
context.Revoked = true;
}
}

18
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs

@ -4,7 +4,7 @@
* the license and the contributors participating to this project.
*/
using System.Diagnostics;
using System;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Server;
@ -17,14 +17,16 @@ namespace OpenIddict.Infrastructure {
public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>();
// Note: the identifier cannot be null as it's always
// generated by the OpenID Connect server middleware
// before invoking the SerializeRefreshToken event.
Debug.Assert(!string.IsNullOrEmpty(context.Ticket.GetTicketId()),
"The unique identifier associated with the refresh token was missing or empty.");
// Persist a new token entry in the database.
var identifier = await services.Tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken);
if (string.IsNullOrEmpty(identifier)) {
throw new InvalidOperationException("The unique key associated with a refresh token cannot be null or empty.");
}
// Only persist the refresh token identifier in the database.
await services.Tokens.CreateAsync(context.Ticket.GetTicketId(), OpenIdConnectConstants.TokenTypeHints.RefreshToken);
// Attach the key returned by the underlying store
// to the refresh token to override the default GUID
// generated by the OpenID Connect server middleware.
context.Ticket.SetTicketId(identifier);
}
}
}

36
src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs

@ -52,8 +52,11 @@ namespace OpenIddict {
/// Creates a new application.
/// </summary>
/// <param name="application">The application to create.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
public virtual Task CreateAsync(TApplication application) {
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the application.
/// </returns>
public virtual Task<string> CreateAsync(TApplication application) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
@ -65,7 +68,10 @@ namespace OpenIddict {
/// Retrieves an application using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the application.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier.
/// </returns>
public virtual Task<TApplication> FindByIdAsync(string identifier) {
return Store.FindByIdAsync(identifier, CancellationToken);
}
@ -74,7 +80,10 @@ namespace OpenIddict {
/// Retrieves an application using its post_logout_redirect_uri.
/// </summary>
/// <param name="url">The post_logout_redirect_uri associated with the application.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns the client application corresponding to the post_logout_redirect_uri.
/// </returns>
public virtual Task<TApplication> FindByLogoutRedirectUri(string url) {
return Store.FindByLogoutRedirectUri(url, CancellationToken);
}
@ -83,7 +92,10 @@ namespace OpenIddict {
/// Retrieves the client type associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client type of the application (by default, "public").
/// </returns>
public virtual async Task<string> GetClientTypeAsync(TApplication application) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
@ -105,7 +117,10 @@ namespace OpenIddict {
/// Retrieves the display name associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the display name associated with the application.
/// </returns>
public virtual Task<string> GetDisplayNameAsync(TApplication application) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
@ -119,7 +134,10 @@ namespace OpenIddict {
/// </summary>
/// <param name="application">The application.</param>
/// <param name="address">The address that should be compared to the redirect_uri stored in the database.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the redirect_uri was valid.
/// </returns>
public virtual async Task<bool> ValidateRedirectUriAsync(TApplication application, string address) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
@ -141,6 +159,10 @@ namespace OpenIddict {
/// <param name="application">The application.</param>
/// <param name="secret">The secret that should be compared to the client_secret stored in the database.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the client secret was valid.
/// </returns>
public virtual async Task<bool> ValidateSecretAsync(TApplication application, string secret) {
if (application == null) {
throw new ArgumentNullException(nameof(application));

26
src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs

@ -75,7 +75,10 @@ namespace OpenIddict {
/// </summary>
/// <param name="user">The user corresponding to the identity.</param>
/// <param name="scopes">The scopes granted by the resource owner.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the <see cref="ClaimsIdentity"/> corresponding to the user.
/// </returns>
public virtual async Task<ClaimsIdentity> CreateIdentityAsync(TUser user, IEnumerable<string> scopes) {
if (user == null) {
throw new ArgumentNullException(nameof(user));
@ -145,26 +148,27 @@ namespace OpenIddict {
/// <summary>
/// Creates a new token, defined by a unique identifier and a token type.
/// </summary>
/// <param name="identifier">The unique identifier associated with the token to create.</param>
/// <param name="type">The token type.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
public virtual Task CreateAsync(string identifier, string type) {
if (string.IsNullOrEmpty(identifier)) {
throw new ArgumentException("The identifier cannot be null or empty", nameof(identifier));
}
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the token.
/// </returns>
public virtual Task<string> CreateAsync(string type) {
if (string.IsNullOrEmpty(type)) {
throw new ArgumentException("The token type cannot be null or empty", nameof(type));
}
return Store.CreateAsync(identifier, type, CancellationToken);
return Store.CreateAsync(type, CancellationToken);
}
/// <summary>
/// Retrieves an token using its unique identifier.
/// Retrieves a token using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the token.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the token corresponding to the unique identifier.
/// </returns>
public virtual Task<TToken> FindByIdAsync(string identifier) {
if (string.IsNullOrEmpty(identifier)) {
throw new ArgumentException("The identifier cannot be null or empty", nameof(identifier));

37
src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs

@ -18,15 +18,21 @@ namespace OpenIddict {
/// </summary>
/// <param name="application">The application to create.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
Task CreateAsync(TApplication application, CancellationToken cancellationToken);
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the application.
/// </returns>
Task<string> CreateAsync(TApplication application, CancellationToken cancellationToken);
/// <summary>
/// Retrieves an application using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier.
/// </returns>
Task<TApplication> FindByIdAsync(string identifier, CancellationToken cancellationToken);
/// <summary>
@ -34,7 +40,10 @@ namespace OpenIddict {
/// </summary>
/// <param name="url">The post_logout_redirect_uri associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns the client application corresponding to the post_logout_redirect_uri.
/// </returns>
Task<TApplication> FindByLogoutRedirectUri(string url, CancellationToken cancellationToken);
/// <summary>
@ -42,7 +51,10 @@ namespace OpenIddict {
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client type of the application (by default, "public").
/// </returns>
Task<string> GetClientTypeAsync(TApplication application, CancellationToken cancellationToken);
/// <summary>
@ -50,7 +62,10 @@ namespace OpenIddict {
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the display name associated with the application.
/// </returns>
Task<string> GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken);
/// <summary>
@ -58,7 +73,10 @@ namespace OpenIddict {
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the redirect_uri associated with the application.
/// </returns>
Task<string> GetRedirectUriAsync(TApplication application, CancellationToken cancellationToken);
/// <summary>
@ -66,7 +84,10 @@ namespace OpenIddict {
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the hashed secret associated with the application.
/// </returns>
Task<string> GetHashedSecretAsync(TApplication application, CancellationToken cancellationToken);
}
}

13
src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs

@ -16,18 +16,23 @@ namespace OpenIddict {
/// <summary>
/// Creates a new token, defined by a unique identifier and a token type.
/// </summary>
/// <param name="identifier">The unique identifier associated with the token to create.</param>
/// <param name="type">The token type.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
Task CreateAsync(string identifier, string type, CancellationToken cancellationToken);
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the token.
/// </returns>
Task<string> CreateAsync(string type, CancellationToken cancellationToken);
/// <summary>
/// Retrieves an token using its unique identifier.
/// </summary>
/// <param name="identifier">The unique identifier associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the token corresponding to the unique identifier.
/// </returns>
Task<TToken> FindByIdAsync(string identifier, CancellationToken cancellationToken);
/// <summary>

2
src/OpenIddict.EntityFramework/Models/OpenIddictApplication.cs

@ -55,6 +55,6 @@ namespace OpenIddict {
/// Gets or sets the application type
/// associated with the current application.
/// </summary>
public virtual string Type { get; set; }
public virtual string Type { get; set; } = OpenIddictConstants.ClientTypes.Public;
}
}

42
src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs

@ -41,14 +41,22 @@ namespace OpenIddict {
/// <param name="application">The application to create.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
public virtual Task CreateAsync(TApplication application, CancellationToken cancellationToken) {
public virtual async Task<string> CreateAsync(TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
// Ensure that the key type can be serialized.
var converter = TypeDescriptor.GetConverter(typeof(TKey));
if (!converter.CanConvertTo(typeof(string))) {
throw new InvalidOperationException($"The '{typeof(TKey).Name}' key type is not supported.");
}
Context.Add(application);
return Context.SaveChangesAsync(cancellationToken);
await Context.SaveChangesAsync(cancellationToken);
return converter.ConvertToInvariantString(application.Id);
}
/// <summary>
@ -56,7 +64,10 @@ namespace OpenIddict {
/// </summary>
/// <param name="identifier">The unique identifier associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client application corresponding to the identifier.
/// </returns>
public virtual Task<TApplication> FindByIdAsync(string identifier, CancellationToken cancellationToken) {
var converter = TypeDescriptor.GetConverter(typeof(TKey));
@ -76,7 +87,10 @@ namespace OpenIddict {
/// </summary>
/// <param name="url">The post_logout_redirect_uri associated with the application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result
/// returns the client application corresponding to the post_logout_redirect_uri.
/// </returns>
public virtual Task<TApplication> FindByLogoutRedirectUri(string url, CancellationToken cancellationToken) {
return Applications.SingleOrDefaultAsync(application => application.LogoutRedirectUri == url, cancellationToken);
}
@ -86,7 +100,10 @@ namespace OpenIddict {
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the client type of the application (by default, "public").
/// </returns>
public virtual Task<string> GetClientTypeAsync(TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
@ -100,7 +117,10 @@ namespace OpenIddict {
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the display name associated with the application.
/// </returns>
public virtual Task<string> GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
@ -114,7 +134,10 @@ namespace OpenIddict {
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the redirect_uri associated with the application.
/// </returns>
public virtual Task<string> GetRedirectUriAsync(TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
@ -128,7 +151,10 @@ namespace OpenIddict {
/// </summary>
/// <param name="application">The application.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the hashed secret associated with the application.
/// </returns>
public virtual Task<string> GetHashedSecretAsync(TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));

31
src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs

@ -38,24 +38,25 @@ namespace OpenIddict {
/// <summary>
/// Creates a new token, defined by a unique identifier and a token type.
/// </summary>
/// <param name="identifier">The unique identifier associated with the token to create.</param>
/// <param name="type">The token type.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
public virtual Task CreateAsync(string identifier, string type, CancellationToken cancellationToken) {
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the unique identifier associated with the token.
/// </returns>
public virtual async Task<string> CreateAsync(string type, CancellationToken cancellationToken) {
// Ensure that the key type can be serialized.
var converter = TypeDescriptor.GetConverter(typeof(TKey));
// Ensure that the key type is compatible with string keys.
if (!converter.CanConvertFrom(typeof(string))) {
return Task.FromResult(0);
if (!converter.CanConvertTo(typeof(string))) {
throw new InvalidOperationException($"The '{typeof(TKey).Name}' key type is not supported.");
}
var key = (TKey) converter.ConvertFromInvariantString(identifier);
var token = new TToken { Id = key, Type = type };
var token = new TToken { Type = type };
Tokens.Add(token);
return Context.SaveChangesAsync(cancellationToken);
await Context.SaveChangesAsync(cancellationToken);
return converter.ConvertToInvariantString(token.Id);
}
/// <summary>
@ -63,12 +64,14 @@ namespace OpenIddict {
/// </summary>
/// <param name="identifier">The unique identifier associated with the token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the token corresponding to the unique identifier.
/// </returns>
public virtual Task<TToken> FindByIdAsync(string identifier, CancellationToken cancellationToken) {
var converter = TypeDescriptor.GetConverter(typeof(TKey));
// If the string key cannot be converted to TKey, return null
// to indicate that the requested token doesn't exist.
var converter = TypeDescriptor.GetConverter(typeof(TKey));
if (!converter.CanConvertFrom(typeof(string))) {
return Task.FromResult<TToken>(null);
}

Loading…
Cancel
Save