diff --git a/src/OpenIddict.Core/Descriptors/OpenIddictAuthorizationDescriptor.cs b/src/OpenIddict.Core/Descriptors/OpenIddictAuthorizationDescriptor.cs
new file mode 100644
index 00000000..927661c8
--- /dev/null
+++ b/src/OpenIddict.Core/Descriptors/OpenIddictAuthorizationDescriptor.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+
+namespace OpenIddict.Core
+{
+ ///
+ /// Represents an OpenIddict authorization descriptor.
+ ///
+ public class OpenIddictAuthorizationDescriptor
+ {
+ ///
+ /// Gets or sets the application identifier associated with the authorization.
+ ///
+ public string ApplicationId { get; set; }
+
+ ///
+ /// Gets or sets the scopes associated with the authorization.
+ ///
+ public IEnumerable Scopes { get; set; }
+
+ ///
+ /// Gets or sets the subject associated with the authorization.
+ ///
+ public string Subject { get; set; }
+ }
+}
diff --git a/src/OpenIddict.Core/Descriptors/OpenIddictTokenDescriptor.cs b/src/OpenIddict.Core/Descriptors/OpenIddictTokenDescriptor.cs
new file mode 100644
index 00000000..724935f5
--- /dev/null
+++ b/src/OpenIddict.Core/Descriptors/OpenIddictTokenDescriptor.cs
@@ -0,0 +1,50 @@
+using System;
+
+namespace OpenIddict.Core
+{
+ ///
+ /// Represents an OpenIddict token descriptor.
+ ///
+ public class OpenIddictTokenDescriptor
+ {
+ ///
+ /// Gets or sets the application identifier associated with the token.
+ ///
+ public string ApplicationId { get; set; }
+
+ ///
+ /// Gets or sets the authorization identifier associated with the token.
+ ///
+ public string AuthorizationId { get; set; }
+
+ ///
+ /// Gets or sets the encrypted payload associated with the token.
+ ///
+ public string Ciphertext { get; set; }
+
+ ///
+ /// Gets or sets the creation date associated with the token.
+ ///
+ public DateTimeOffset? CreationDate { get; set; }
+
+ ///
+ /// Gets or sets the expiration date associated with the token.
+ ///
+ public DateTimeOffset? ExpirationDate { get; set; }
+
+ ///
+ /// Gets or sets the cryptographic hash associated with the token.
+ ///
+ public string Hash { get; set; }
+
+ ///
+ /// Gets or sets the subject associated with the token.
+ ///
+ public string Subject { get; set; }
+
+ ///
+ /// Gets or sets the token type.
+ ///
+ public string Type { get; set; }
+ }
+}
diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
index 2910e60e..cd249bb0 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
@@ -65,33 +65,19 @@ namespace OpenIddict.Core
///
/// Creates a new authorization.
///
- /// The subject associated with the authorization.
- /// The client associated with the authorization.
- /// The scopes associated with the authorization.
+ /// The authorization descriptor.
/// The that can be used to abort the operation.
///
/// A that can be used to monitor the asynchronous operation, whose result returns the authorization.
///
- public virtual Task CreateAsync(
- [NotNull] string subject, [NotNull] string client,
- [NotNull] IEnumerable scopes, CancellationToken cancellationToken)
+ public virtual Task CreateAsync([NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken)
{
- if (scopes == null)
- {
- throw new ArgumentNullException(nameof(scopes));
- }
-
- if (string.IsNullOrEmpty(subject))
- {
- throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
- }
-
- if (string.IsNullOrEmpty(client))
+ if (descriptor == null)
{
- throw new ArgumentException("The client cannot be null or empty.", nameof(subject));
+ throw new ArgumentNullException(nameof(descriptor));
}
- return Store.CreateAsync(subject, client, scopes, cancellationToken);
+ return Store.CreateAsync(descriptor, cancellationToken);
}
///
diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
index f1612b2a..6d32f488 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
@@ -66,65 +66,39 @@ namespace OpenIddict.Core
///
/// Creates a new token, which is associated with a particular subject.
///
- /// The token type.
- /// The subject associated with the token.
- /// The date on which the token will start to be considered valid.
- /// The date on which the token will no longer be considered valid.
+ /// The token descriptor.
/// The that can be used to abort the operation.
///
/// A that can be used to monitor the asynchronous operation, whose result returns the token.
///
- public virtual Task CreateAsync(
- [NotNull] string type, [NotNull] string subject,
- [CanBeNull] DateTimeOffset? start,
- [CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken)
+ public virtual Task CreateAsync([NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken)
{
- if (string.IsNullOrEmpty(type))
+ if (descriptor == null)
{
- throw new ArgumentException("The token type cannot be null or empty.", nameof(type));
+ throw new ArgumentNullException(nameof(descriptor));
}
- if (string.IsNullOrEmpty(subject))
- {
- throw new ArgumentException("The subject cannot be null or empty.");
- }
-
- return Store.CreateAsync(type, subject, start, end, cancellationToken);
+ return Store.CreateAsync(descriptor, cancellationToken);
}
///
- /// Creates a new reference token, which is associated with a particular subject.
+ /// Extends the specified token by replacing its expiration date.
///
- /// The token type.
- /// The subject associated with the token.
- /// The hash of the crypto-secure random identifier associated with the token.
- /// The ciphertext associated with the token.
- /// The date on which the token will start to be considered valid.
- /// The date on which the token will no longer be considered valid.
+ /// The token.
+ /// The date on which the token will no longer be considered valid.
/// The that can be used to abort the operation.
///
- /// A that can be used to monitor the asynchronous operation, whose result returns the token.
+ /// A that can be used to monitor the asynchronous operation.
///
- public virtual Task CreateAsync(
- [NotNull] string type, [NotNull] string subject, [NotNull] string hash, [NotNull] string ciphertext,
- [CanBeNull] DateTimeOffset? start, [CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken)
+ public virtual async Task ExtendAsync([NotNull] TToken token, [CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken)
{
- if (string.IsNullOrEmpty(type))
- {
- throw new ArgumentException("The token type cannot be null or empty.", nameof(type));
- }
-
- if (string.IsNullOrEmpty(subject))
- {
- throw new ArgumentException("The subject cannot be null or empty.");
- }
-
- if (string.IsNullOrEmpty(ciphertext))
+ if (token == null)
{
- throw new ArgumentException("The ciphertext cannot be null or empty.", nameof(ciphertext));
+ throw new ArgumentNullException(nameof(token));
}
- return Store.CreateAsync(type, subject, hash, ciphertext, start, end, cancellationToken);
+ await Store.SetExpirationDateAsync(token, date, cancellationToken);
+ await UpdateAsync(token, cancellationToken);
}
///
@@ -221,6 +195,44 @@ namespace OpenIddict.Core
return Store.GetCiphertextAsync(token, cancellationToken);
}
+ ///
+ /// Retrieves the creation date associated with a token.
+ ///
+ /// The token.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the creation date associated with the specified token.
+ ///
+ public virtual Task GetCreationDateAsync([NotNull] TToken token, CancellationToken cancellationToken)
+ {
+ if (token == null)
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ return Store.GetCreationDateAsync(token, cancellationToken);
+ }
+
+ ///
+ /// Retrieves the expiration date associated with a token.
+ ///
+ /// The token.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the expiration date associated with the specified token.
+ ///
+ public virtual Task GetExpirationDateAsync([NotNull] TToken token, CancellationToken cancellationToken)
+ {
+ if (token == null)
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ return Store.GetExpirationDateAsync(token, cancellationToken);
+ }
+
///
/// Retrieves the hashed identifier associated with a token.
///
diff --git a/src/OpenIddict.Core/OpenIddictConstants.cs b/src/OpenIddict.Core/OpenIddictConstants.cs
index 97b901b8..65ad9ba7 100644
--- a/src/OpenIddict.Core/OpenIddictConstants.cs
+++ b/src/OpenIddict.Core/OpenIddictConstants.cs
@@ -33,6 +33,7 @@ namespace OpenIddict.Core
public static class Properties
{
public const string AuthorizationId = ".authorization_id";
+ public const string TokenId = ".token_id";
}
public static class Scopes
diff --git a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs
index 269d6fd5..bb5395e2 100644
--- a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs
+++ b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs
@@ -36,16 +36,12 @@ namespace OpenIddict.Core
///
/// Creates a new authorization.
///
- /// The subject associated with the authorization.
- /// The client associated with the authorization.
- /// The scopes associated with the authorization.
+ /// The authorization descriptor.
/// The that can be used to abort the operation.
///
/// A that can be used to monitor the asynchronous operation, whose result returns the authorization.
///
- Task CreateAsync(
- [NotNull] string subject, [NotNull] string client,
- [NotNull] IEnumerable scopes, CancellationToken cancellationToken);
+ Task CreateAsync([NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken);
///
/// Retrieves an authorization using its unique identifier.
diff --git a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs
index 5e57edd7..67491003 100644
--- a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs
+++ b/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs
@@ -36,35 +36,12 @@ namespace OpenIddict.Core
///
/// Creates a new token, which is associated with a particular subject.
///
- /// The token type.
- /// The subject associated with the token.
- /// The date on which the token will start to be considered valid.
- /// The date on which the token will no longer be considered valid.
+ /// The token descriptor.
/// The that can be used to abort the operation.
///
/// A that can be used to monitor the asynchronous operation, whose result returns the token.
///
- Task CreateAsync(
- [NotNull] string type, [NotNull] string subject,
- [CanBeNull] DateTimeOffset? start,
- [CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken);
-
- ///
- /// Creates a new reference token, which is associated with a particular subject.
- ///
- /// The token type.
- /// The subject associated with the token.
- /// The hash of the crypto-secure random identifier associated with the token.
- /// The ciphertext associated with the token.
- /// The date on which the token will start to be considered valid.
- /// The date on which the token will no longer be considered valid.
- /// The that can be used to abort the operation.
- ///
- /// A that can be used to monitor the asynchronous operation, whose result returns the token.
- ///
- Task CreateAsync(
- [NotNull] string type, [NotNull] string subject, [NotNull] string hash, [NotNull] string ciphertext,
- [CanBeNull] DateTimeOffset? start, [CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken);
+ Task CreateAsync([NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken);
///
/// Removes a token.
@@ -140,6 +117,28 @@ namespace OpenIddict.Core
///
Task GetCiphertextAsync([NotNull] TToken token, CancellationToken cancellationToken);
+ ///
+ /// Retrieves the creation date associated with a token.
+ ///
+ /// The token.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the creation date associated with the specified token.
+ ///
+ Task GetCreationDateAsync([NotNull] TToken token, CancellationToken cancellationToken);
+
+ ///
+ /// Retrieves the expiration date associated with a token.
+ ///
+ /// The token.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the expiration date associated with the specified token.
+ ///
+ Task GetExpirationDateAsync([NotNull] TToken token, CancellationToken cancellationToken);
+
///
/// Retrieves the hashed identifier associated with a token.
///
@@ -217,6 +216,17 @@ namespace OpenIddict.Core
///
Task SetClientAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken);
+ ///
+ /// Sets the expiration date associated with a token.
+ ///
+ /// The token.
+ /// The date on which the token will no longer be considered valid.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ Task SetExpirationDateAsync([NotNull] TToken token, [CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken);
+
///
/// Sets the status associated with a token.
///
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
index d6388e61..f6199345 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
@@ -113,28 +113,19 @@ namespace OpenIddict.EntityFrameworkCore
///
/// Creates a new authorization.
///
- /// The subject associated with the authorization.
- /// The client associated with the authorization.
- /// The scopes associated with the authorization.
+ /// The authorization descriptor.
/// The that can be used to abort the operation.
///
/// A that can be used to monitor the asynchronous operation, whose result returns the authorization.
///
- public virtual async Task CreateAsync(
- [NotNull] string subject, [NotNull] string client,
- [NotNull] IEnumerable scopes, CancellationToken cancellationToken)
+ public virtual async Task CreateAsync([NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken)
{
- if (string.IsNullOrEmpty(subject))
- {
- throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
- }
-
- if (string.IsNullOrEmpty(client))
+ if (descriptor == null)
{
- throw new ArgumentException("The client cannot be null or empty.", nameof(subject));
+ throw new ArgumentNullException(nameof(descriptor));
}
- var key = ConvertIdentifierFromString(client);
+ var key = ConvertIdentifierFromString(descriptor.ApplicationId);
var application = await Applications.SingleOrDefaultAsync(entity => entity.Id.Equals(key));
if (application == null)
@@ -145,8 +136,8 @@ namespace OpenIddict.EntityFrameworkCore
var authorization = new TAuthorization
{
Application = application,
- Scope = string.Join(" ", scopes),
- Subject = subject
+ Scope = string.Join(" ", descriptor.Scopes),
+ Subject = descriptor.Subject
};
return await CreateAsync(authorization, cancellationToken);
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
index aac99c01..c7374c1e 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
@@ -117,78 +117,51 @@ namespace OpenIddict.EntityFrameworkCore
///
/// Creates a new token, which is associated with a particular subject.
///
- /// The token type.
- /// The subject associated with the token.
- /// The date on which the token will start to be considered valid.
- /// The date on which the token will no longer be considered valid.
+ /// The token descriptor.
/// The that can be used to abort the operation.
///
/// A that can be used to monitor the asynchronous operation, whose result returns the token.
///
- public virtual Task CreateAsync(
- [NotNull] string type, [NotNull] string subject,
- [CanBeNull] DateTimeOffset? start,
- [CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken)
+ public virtual async Task CreateAsync([NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken)
{
- if (string.IsNullOrEmpty(type))
+ if (descriptor == null)
{
- throw new ArgumentException("The token type cannot be null or empty.");
- }
-
- if (string.IsNullOrEmpty(subject))
- {
- throw new ArgumentException("The subject cannot be null or empty.");
+ throw new ArgumentNullException(nameof(descriptor));
}
var token = new TToken
{
- End = end,
- Start = start,
- Subject = subject,
- Type = type
+ Ciphertext = descriptor.Ciphertext,
+ CreationDate = descriptor.CreationDate,
+ ExpirationDate = descriptor.ExpirationDate,
+ Hash = descriptor.Hash,
+ Subject = descriptor.Subject,
+ Type = descriptor.Type
};
- return CreateAsync(token, cancellationToken);
- }
+ // Bind the token to the specified client application.
+ var key = ConvertIdentifierFromString(descriptor.ApplicationId);
- ///
- /// Creates a new reference token, which is associated with a particular subject.
- ///
- /// The token type.
- /// The subject associated with the token.
- /// The hash of the crypto-secure random identifier associated with the token.
- /// The ciphertext associated with the token.
- /// The date on which the token will start to be considered valid.
- /// The date on which the token will no longer be considered valid.
- /// The that can be used to abort the operation.
- ///
- /// A that can be used to monitor the asynchronous operation, whose result returns the token.
- ///
- public virtual Task CreateAsync(
- [NotNull] string type, [NotNull] string subject, [NotNull] string hash, [NotNull] string ciphertext,
- [CanBeNull] DateTimeOffset? start, [CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken)
- {
- if (string.IsNullOrEmpty(type))
+ var application = await Applications.SingleOrDefaultAsync(entity => entity.Id.Equals(key));
+ if (application == null)
{
- throw new ArgumentException("The token type cannot be null or empty.");
+ throw new InvalidOperationException("The application associated with the token cannot be found.");
}
- if (string.IsNullOrEmpty(subject))
+ token.Application = application;
+
+ // Bind the token to the specified authorization.
+ key = ConvertIdentifierFromString(descriptor.AuthorizationId);
+
+ var authorization = await Authorizations.SingleOrDefaultAsync(entity => entity.Id.Equals(key));
+ if (authorization == null)
{
- throw new ArgumentException("The subject cannot be null or empty.");
+ throw new InvalidOperationException("The authorization associated with the token cannot be found.");
}
- var token = new TToken
- {
- Ciphertext = ciphertext,
- End = end,
- Hash = hash,
- Start = start,
- Subject = subject,
- Type = type
- };
+ token.Authorization = authorization;
- return CreateAsync(token, cancellationToken);
+ return await CreateAsync(token, cancellationToken);
}
///
@@ -316,6 +289,44 @@ namespace OpenIddict.EntityFrameworkCore
return Task.FromResult(token.Ciphertext);
}
+ ///
+ /// Retrieves the creation date associated with a token.
+ ///
+ /// The token.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the creation date associated with the specified token.
+ ///
+ public virtual Task GetCreationDateAsync([NotNull] TToken token, CancellationToken cancellationToken)
+ {
+ if (token == null)
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ return Task.FromResult(token.CreationDate);
+ }
+
+ ///
+ /// Retrieves the expiration date associated with a token.
+ ///
+ /// The token.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation,
+ /// whose result returns the expiration date associated with the specified token.
+ ///
+ public virtual Task GetExpirationDateAsync([NotNull] TToken token, CancellationToken cancellationToken)
+ {
+ if (token == null)
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ return Task.FromResult(token.ExpirationDate);
+ }
+
///
/// Retrieves the hashed identifier associated with a token.
///
@@ -497,6 +508,23 @@ namespace OpenIddict.EntityFrameworkCore
}
}
+ ///
+ /// Sets the expiration date associated with a token.
+ ///
+ /// The token.
+ /// The date on which the token will no longer be considered valid.
+ /// The that can be used to abort the operation.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public virtual Task SetExpirationDateAsync([NotNull] TToken token,
+ [CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken)
+ {
+ token.ExpirationDate = date;
+
+ return Task.CompletedTask;
+ }
+
///
/// Sets the status associated with a token.
///
diff --git a/src/OpenIddict.Models/OpenIddictToken.cs b/src/OpenIddict.Models/OpenIddictToken.cs
index f9926574..aeef2b6a 100644
--- a/src/OpenIddict.Models/OpenIddictToken.cs
+++ b/src/OpenIddict.Models/OpenIddictToken.cs
@@ -50,11 +50,17 @@ namespace OpenIddict.Models
///
public virtual string Ciphertext { get; set; }
+ ///
+ /// Gets or sets the date on which the token
+ /// will start to be considered valid.
+ ///
+ public virtual DateTimeOffset? CreationDate { get; set; }
+
///
/// Gets or sets the date on which the token
/// will no longer be considered valid.
///
- public virtual DateTimeOffset? End { get; set; }
+ public virtual DateTimeOffset? ExpirationDate { get; set; }
///
/// Gets or sets the hashed identifier associated
@@ -69,12 +75,6 @@ namespace OpenIddict.Models
///
public virtual TKey Id { get; set; }
- ///
- /// Gets or sets the date on which the token
- /// will start to be considered valid.
- ///
- public virtual DateTimeOffset? Start { get; set; }
-
///
/// Gets or sets the status of the current token.
///
diff --git a/src/OpenIddict/OpenIddictExtensions.cs b/src/OpenIddict/OpenIddictExtensions.cs
index 91746777..24810e50 100644
--- a/src/OpenIddict/OpenIddictExtensions.cs
+++ b/src/OpenIddict/OpenIddictExtensions.cs
@@ -940,5 +940,23 @@ namespace Microsoft.AspNetCore.Builder
return builder.Configure(options => options.UseReferenceTokens = true);
}
+
+ ///
+ /// Configures OpenIddict to use rolling refresh tokens. When this option is enabled,
+ /// a new refresh token is issued for each refresh token request and the previous one
+ /// is automatically revoked (when disabled, no new refresh token is issued and the
+ /// lifetime of the original refresh token is increased by updating the database entry).
+ ///
+ /// The services builder used by OpenIddict to register new services.
+ /// The .
+ public static OpenIddictBuilder UseRollingTokens([NotNull] this OpenIddictBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ return builder.Configure(options => options.UseRollingTokens = true);
+ }
}
}
\ No newline at end of file
diff --git a/src/OpenIddict/OpenIddictInitializer.cs b/src/OpenIddict/OpenIddictInitializer.cs
index fe7e5744..16a52332 100644
--- a/src/OpenIddict/OpenIddictInitializer.cs
+++ b/src/OpenIddict/OpenIddictInitializer.cs
@@ -158,6 +158,16 @@ namespace OpenIddict
"Reference tokens cannot be used when configuring JWT as the access token format.");
}
+ if (options.UseRollingTokens && options.DisableTokenRevocation)
+ {
+ throw new InvalidOperationException("Rolling tokens cannot be used when disabling token expiration.");
+ }
+
+ if (options.UseRollingTokens && !options.UseSlidingExpiration)
+ {
+ throw new InvalidOperationException("Rolling tokens cannot be used without enabling sliding expiration.");
+ }
+
if (options.AccessTokenHandler != null && options.SigningCredentials.Count == 0)
{
throw new InvalidOperationException(
diff --git a/src/OpenIddict/OpenIddictOptions.cs b/src/OpenIddict/OpenIddictOptions.cs
index f6b00f5e..6e35223f 100644
--- a/src/OpenIddict/OpenIddictOptions.cs
+++ b/src/OpenIddict/OpenIddictOptions.cs
@@ -87,5 +87,14 @@ namespace OpenIddict
/// Note: this option cannot be used when configuring JWT as the access token format.
///
public bool UseReferenceTokens { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether rolling tokens should be used.
+ /// When disabled, no new token is issued and the refresh token lifetime is
+ /// dynamically managed by updating the token entry in the database.
+ /// When this option is enabled, a new refresh token is issued for each
+ /// refresh token request and the previous one is automatically revoked.
+ ///
+ public bool UseRollingTokens { get; set; }
}
}
diff --git a/src/OpenIddict/OpenIddictProvider.Exchange.cs b/src/OpenIddict/OpenIddictProvider.Exchange.cs
index 82b6a3fe..47d83703 100644
--- a/src/OpenIddict/OpenIddictProvider.Exchange.cs
+++ b/src/OpenIddict/OpenIddictProvider.Exchange.cs
@@ -214,6 +214,9 @@ namespace OpenIddict
var identifier = context.Ticket.GetProperty(OpenIdConnectConstants.Properties.TokenId);
Debug.Assert(!string.IsNullOrEmpty(identifier), "The authentication ticket should contain a ticket identifier.");
+ // Store the original authorization code/refresh token so it can be later retrieved.
+ context.Request.SetProperty(OpenIddictConstants.Properties.TokenId, identifier);
+
if (context.Request.IsAuthorizationCodeGrantType())
{
// Retrieve the authorization code from the database and ensure it is still valid.
@@ -259,7 +262,7 @@ namespace OpenIddict
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
- description: "The specified authorization code has already been redemeed.");
+ description: "The specified authorization code has already been redeemed.");
return;
}
@@ -283,7 +286,29 @@ namespace OpenIddict
{
// Retrieve the token from the database and ensure it is still valid.
var token = await Tokens.FindByIdAsync(identifier, context.HttpContext.RequestAborted);
- if (token == null || !await Tokens.IsValidAsync(token, context.HttpContext.RequestAborted))
+ if (token == null)
+ {
+ Logger.LogError("The token request was rejected because the refresh token was already redeemed.");
+
+ context.Reject(
+ error: OpenIdConnectConstants.Errors.InvalidGrant,
+ description: "The specified refresh token is no longer valid.");
+
+ return;
+ }
+
+ else if (await Tokens.IsRedeemedAsync(token, context.HttpContext.RequestAborted))
+ {
+ Logger.LogError("The token request was rejected because the refresh token was no longer valid.");
+
+ context.Reject(
+ error: OpenIdConnectConstants.Errors.InvalidGrant,
+ description: "The specified refresh token has already been redeemed.");
+
+ return;
+ }
+
+ else if (!await Tokens.IsValidAsync(token, context.HttpContext.RequestAborted))
{
Logger.LogError("The token request was rejected because the refresh token was no longer valid.");
@@ -294,10 +319,10 @@ namespace OpenIddict
return;
}
- // When sliding expiration is enabled, immediately
+ // When rolling tokens are enabled, immediately
// redeem the refresh token to prevent future reuse.
// See https://tools.ietf.org/html/rfc6749#section-6.
- if (options.UseSlidingExpiration)
+ if (options.UseRollingTokens)
{
await Tokens.RedeemAsync(token, context.HttpContext.RequestAborted);
}
diff --git a/src/OpenIddict/OpenIddictProvider.Serialization.cs b/src/OpenIddict/OpenIddictProvider.Serialization.cs
index 6133e488..739c6147 100644
--- a/src/OpenIddict/OpenIddictProvider.Serialization.cs
+++ b/src/OpenIddict/OpenIddictProvider.Serialization.cs
@@ -7,7 +7,6 @@
using System;
using System.Diagnostics;
using System.Security.Cryptography;
-using System.Threading;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
@@ -117,6 +116,8 @@ namespace OpenIddict
public override async Task SerializeAuthorizationCode([NotNull] SerializeAuthorizationCodeContext context)
{
+ Debug.Assert(context.Request.IsAuthorizationRequest(), "The request should be an authorization request.");
+
var token = await CreateTokenAsync(
OpenIdConnectConstants.TokenUsages.AuthorizationCode,
context.Ticket, (OpenIddictOptions) context.Options,
@@ -136,9 +137,36 @@ namespace OpenIddict
public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context)
{
+ var options = (OpenIddictOptions) context.Options;
+
+ Debug.Assert(context.Request.IsTokenRequest(), "The request should be a token request.");
+
+ // When rolling tokens are disabled, extend the expiration date associated with the
+ // existing token instead of returning a new refresh token with a new expiration date.
+ if (options.UseSlidingExpiration && !options.UseRollingTokens && context.Request.IsRefreshTokenGrantType())
+ {
+ var identifier = context.Request.GetProperty(OpenIddictConstants.Properties.TokenId);
+
+ var entry = await Tokens.FindByIdAsync(identifier, context.HttpContext.RequestAborted);
+ if (entry != null)
+ {
+ Logger.LogInformation("The expiration date of the '{Identifier}' token was automatically updated: {Date}.",
+ identifier, context.Ticket.Properties.ExpiresUtc);
+
+ await Tokens.ExtendAsync(entry, context.Ticket.Properties.ExpiresUtc, context.HttpContext.RequestAborted);
+
+ context.RefreshToken = null;
+ context.HandleSerialization();
+
+ return;
+ }
+
+ // If the refresh token entry could not be
+ // found in the database, generate a new one.
+ }
+
var token = await CreateTokenAsync(
- OpenIdConnectConstants.TokenUsages.RefreshToken,
- context.Ticket, (OpenIddictOptions) context.Options,
+ OpenIdConnectConstants.TokenUsages.RefreshToken, context.Ticket, options,
context.HttpContext, context.Request, context.DataFormat);
// If a reference token was returned by CreateTokenAsync(),
@@ -162,7 +190,10 @@ namespace OpenIddict
Debug.Assert(!(options.DisableTokenRevocation && options.UseReferenceTokens),
"Token revocation cannot be disabled when using reference tokens.");
- Debug.Assert(!string.Equals(type, OpenIdConnectConstants.TokenUsages.IdToken, StringComparison.OrdinalIgnoreCase),
+ Debug.Assert(!(options.DisableTokenRevocation && options.UseRollingTokens),
+ "Token revocation cannot be disabled when using rolling tokens.");
+
+ Debug.Assert(type != OpenIdConnectConstants.TokenUsages.IdToken,
"Identity tokens shouldn't be stored in the database.");
if (options.DisableTokenRevocation)
@@ -170,28 +201,34 @@ namespace OpenIddict
return null;
}
- // Resolve the subject from the authentication ticket. If it cannot be found, throw an exception.
- var subject = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject);
- if (string.IsNullOrEmpty(subject))
+ var descriptor = new OpenIddictTokenDescriptor
{
- throw new InvalidOperationException("The subject associated with the authentication ticket cannot be retrieved.");
- }
+ CreationDate = ticket.Properties.IssuedUtc,
+ ExpirationDate = ticket.Properties.ExpiresUtc,
+ Subject = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject),
+ Type = type
+ };
- TToken token;
string result = null;
+ // When reference tokens are enabled or when the token is an authorization code or a
+ // refresh token, remove the unnecessary properties from the authentication ticket.
+ if (options.UseReferenceTokens ||
+ (type == OpenIdConnectConstants.TokenUsages.AuthorizationCode ||
+ type == OpenIdConnectConstants.TokenUsages.RefreshToken))
+ {
+ ticket.Properties.IssuedUtc = ticket.Properties.ExpiresUtc = null;
+ ticket.RemoveProperty(OpenIdConnectConstants.Properties.TokenId);
+ }
+
// If reference tokens are enabled, create a new entry for
// authorization codes, refresh tokens and access tokens.
if (options.UseReferenceTokens)
{
- // When the token is a reference token, remove the token identifier from the
- // authentication ticket as it is restored when receiving and decrypting it.
- ticket.RemoveProperty(OpenIdConnectConstants.Properties.TokenId);
-
// Note: the data format is automatically replaced at startup time to ensure
// that encrypted tokens stored in the database cannot be considered as
// valid tokens if the developer decides to disable reference tokens support.
- var ciphertext = format.Protect(ticket);
+ descriptor.Ciphertext = format.Protect(ticket);
// Generate a new crypto-secure random identifier that will be
// substituted to the ciphertext returned by the data format.
@@ -203,49 +240,19 @@ namespace OpenIddict
// it as the hashed identifier of the reference token.
// Doing that prevents token identifiers stolen from
// the database from being used as valid reference tokens.
- string hash;
using (var algorithm = SHA256.Create())
{
- hash = Convert.ToBase64String(algorithm.ComputeHash(bytes));
+ descriptor.Hash = Convert.ToBase64String(algorithm.ComputeHash(bytes));
}
-
- token = await Tokens.CreateAsync(type, subject, hash, ciphertext,
- ticket.Properties.IssuedUtc,
- ticket.Properties.ExpiresUtc, context.RequestAborted);
}
// Otherwise, only create a token metadata entry for authorization codes and refresh tokens.
- else if (string.Equals(type, OpenIdConnectConstants.TokenUsages.AuthorizationCode, StringComparison.OrdinalIgnoreCase) ||
- string.Equals(type, OpenIdConnectConstants.TokenUsages.RefreshToken, StringComparison.OrdinalIgnoreCase))
- {
- token = await Tokens.CreateAsync(type, subject,
- ticket.Properties.IssuedUtc,
- ticket.Properties.ExpiresUtc, context.RequestAborted);
- }
-
- else
- {
- return null;
- }
-
- // If a null value was returned by CreateAsync(), return immediately.
- if (token == null)
+ else if (type != OpenIdConnectConstants.TokenUsages.AuthorizationCode &&
+ type != OpenIdConnectConstants.TokenUsages.RefreshToken)
{
return null;
}
- // Throw an exception if the token identifier can't be resolved.
- var identifier = await Tokens.GetIdAsync(token, context.RequestAborted);
- if (string.IsNullOrEmpty(identifier))
- {
- throw new InvalidOperationException("The unique key associated with a refresh token cannot be null or empty.");
- }
-
- // Attach the key returned by the underlying store
- // to the refresh token to override the default GUID
- // generated by the OpenID Connect server middleware.
- ticket.SetTokenId(identifier);
-
// If the client application is known, associate it with the token.
if (!string.IsNullOrEmpty(request.ClientId))
{
@@ -255,41 +262,61 @@ namespace OpenIddict
throw new InvalidOperationException("The client application cannot be retrieved from the database.");
}
- var key = await Applications.GetIdAsync(application, context.RequestAborted);
-
- await Tokens.SetClientAsync(token, key, context.RequestAborted);
+ descriptor.ApplicationId = await Applications.GetIdAsync(application, context.RequestAborted);
}
// If an authorization identifier was specified, bind it to the token.
if (ticket.HasProperty(OpenIddictConstants.Properties.AuthorizationId))
{
- await Tokens.SetAuthorizationAsync(token,
- ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId), context.RequestAborted);
+ descriptor.AuthorizationId = ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId);
}
// Otherwise, create an ad-hoc authorization if the token is an authorization code.
- else if (string.Equals(type, OpenIdConnectConstants.TokenUsages.AuthorizationCode, StringComparison.OrdinalIgnoreCase))
+ else if (type == OpenIdConnectConstants.TokenUsages.AuthorizationCode)
{
- Debug.Assert(!string.IsNullOrEmpty(request.ClientId), "The client identifier shouldn't be null.");
+ Debug.Assert(!string.IsNullOrEmpty(descriptor.ApplicationId), "The client identifier shouldn't be null.");
- var application = await Applications.FindByClientIdAsync(request.ClientId, context.RequestAborted);
- if (application == null)
+ var authorization = await Authorizations.CreateAsync(new OpenIddictAuthorizationDescriptor
{
- throw new InvalidOperationException("The client application cannot be retrieved from the database.");
- }
-
- var authorization = await Authorizations.CreateAsync(subject,
- await Applications.GetIdAsync(application, context.RequestAborted), request.GetScopes(), context.RequestAborted);
+ ApplicationId = descriptor.ApplicationId,
+ Scopes = request.GetScopes(),
+ Subject = descriptor.Subject
+ }, context.RequestAborted);
if (authorization != null)
{
- var key = await Authorizations.GetIdAsync(authorization, context.RequestAborted);
- ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, key);
+ descriptor.AuthorizationId = await Authorizations.GetIdAsync(authorization, context.RequestAborted);
- await Tokens.SetAuthorizationAsync(token, key, context.RequestAborted);
+ Logger.LogInformation("An ad-hoc authorization was automatically created and " +
+ "associated with the '{ClientId}' application: {Identifier}.",
+ request.ClientId, descriptor.AuthorizationId);
}
}
+ // If a null value was returned by CreateAsync(), return immediately.
+ var token = await Tokens.CreateAsync(descriptor, context.RequestAborted);
+ if (token == null)
+ {
+ return null;
+ }
+
+ // Throw an exception if the token identifier can't be resolved.
+ var identifier = await Tokens.GetIdAsync(token, context.RequestAborted);
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new InvalidOperationException("The unique key associated with a refresh token cannot be null or empty.");
+ }
+
+ // Restore the token identifier using the unique
+ // identifier attached with the database entry.
+ ticket.SetTokenId(identifier);
+
+ // Dynamically set the creation and expiration dates.
+ ticket.Properties.IssuedUtc = await Tokens.GetCreationDateAsync(token, context.RequestAborted);
+ ticket.Properties.ExpiresUtc = await Tokens.GetExpirationDateAsync(token, context.RequestAborted);
+
+ ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, descriptor.AuthorizationId);
+
if (!string.IsNullOrEmpty(result))
{
Logger.LogTrace("A new reference token was successfully generated and persisted " +
@@ -373,6 +400,10 @@ namespace OpenIddict
// identifier attached with the database entry.
ticket.SetTokenId(identifier);
+ // Dynamically set the creation and expiration dates.
+ ticket.Properties.IssuedUtc = await Tokens.GetCreationDateAsync(token, context.RequestAborted);
+ ticket.Properties.ExpiresUtc = await Tokens.GetExpirationDateAsync(token, context.RequestAborted);
+
// If the authorization identifier cannot be found in the ticket properties,
// try to restore it using the identifier associated with the database entry.
if (!ticket.HasProperty(OpenIddictConstants.Properties.AuthorizationId))
diff --git a/test/OpenIddict.Tests/OpenIddictInitializerTests.cs b/test/OpenIddict.Tests/OpenIddictInitializerTests.cs
index af5a7f44..19782a02 100644
--- a/test/OpenIddict.Tests/OpenIddictInitializerTests.cs
+++ b/test/OpenIddict.Tests/OpenIddictInitializerTests.cs
@@ -155,6 +155,52 @@ namespace OpenIddict.Tests
Assert.Equal("Reference tokens cannot be used when disabling token revocation.", exception.Message);
}
+ [Fact]
+ public async Task PostConfigure_ThrowsAnExceptionWhenUsingRollingTokensWithTokenRevocationDisabled()
+ {
+ // Arrange
+ var server = CreateAuthorizationServer(builder =>
+ {
+ builder.EnableAuthorizationEndpoint("/connect/authorize")
+ .AllowImplicitFlow()
+ .DisableTokenRevocation()
+ .UseRollingTokens();
+ });
+
+ var client = new OpenIdConnectClient(server.CreateClient());
+
+ // Act and assert
+ var exception = await Assert.ThrowsAsync(delegate
+ {
+ return client.GetAsync("/");
+ });
+
+ Assert.Equal("Rolling tokens cannot be used when disabling token expiration.", exception.Message);
+ }
+
+ [Fact]
+ public async Task PostConfigure_ThrowsAnExceptionWhenUsingRollingTokensWithSlidingExpirationDisabled()
+ {
+ // Arrange
+ var server = CreateAuthorizationServer(builder =>
+ {
+ builder.EnableAuthorizationEndpoint("/connect/authorize")
+ .AllowImplicitFlow()
+ .UseRollingTokens()
+ .Configure(options => options.UseSlidingExpiration = false);
+ });
+
+ var client = new OpenIdConnectClient(server.CreateClient());
+
+ // Act and assert
+ var exception = await Assert.ThrowsAsync(delegate
+ {
+ return client.GetAsync("/");
+ });
+
+ Assert.Equal("Rolling tokens cannot be used without enabling sliding expiration.", exception.Message);
+ }
+
[Fact]
public async Task PostConfigure_ThrowsAnExceptionWhenUsingReferenceTokensIfAnAccessTokenHandlerIsSet()
{
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs
index 26a8a9f6..47ab37df 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs
@@ -554,16 +554,16 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
+
+ instance.Setup(mock => mock.GetIdAsync(application, It.IsAny()))
+ .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
}));
builder.Services.AddSingleton(CreateTokenManager(instance =>
{
var token = new OpenIddictToken();
- instance.Setup(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
- It.IsAny(), It.IsAny(),
- It.IsAny()))
+ instance.Setup(mock => mock.CreateAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny()))
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs
index fd1034eb..9a653281 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs
@@ -577,7 +577,7 @@ namespace OpenIddict.Tests
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
- Assert.Equal("The specified authorization code has already been redemeed.", response.ErrorDescription);
+ Assert.Equal("The specified authorization code has already been redeemed.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.Once());
Mock.Get(manager).Verify(mock => mock.IsRedeemedAsync(token, It.IsAny()), Times.Once());
@@ -660,7 +660,7 @@ namespace OpenIddict.Tests
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
- Assert.Equal("The specified authorization code has already been redemeed.", response.ErrorDescription);
+ Assert.Equal("The specified authorization code has already been redeemed.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.Once());
Mock.Get(manager).Verify(mock => mock.IsRedeemedAsync(tokens[0], It.IsAny()), Times.Once());
@@ -796,6 +796,69 @@ namespace OpenIddict.Tests
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.Once());
}
+ [Fact]
+ public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsAlreadyRedeemed()
+ {
+ // Arrange
+ var ticket = new AuthenticationTicket(
+ new ClaimsPrincipal(),
+ new AuthenticationProperties(),
+ OpenIdConnectServerDefaults.AuthenticationScheme);
+
+ ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
+ ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
+
+ var format = new Mock>();
+
+ format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
+ .Returns(ticket);
+
+ var token = new OpenIddictToken();
+
+ var manager = CreateTokenManager(instance =>
+ {
+ instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()))
+ .ReturnsAsync(token);
+
+ instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny()))
+ .ReturnsAsync(true);
+ });
+
+ var server = CreateAuthorizationServer(builder =>
+ {
+ builder.Services.AddSingleton(CreateApplicationManager(instance =>
+ {
+ var application = new OpenIddictApplication();
+
+ instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny()))
+ .ReturnsAsync(application);
+
+ instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny()))
+ .ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
+ }));
+
+ builder.Services.AddSingleton(manager);
+
+ builder.Configure(options => options.RefreshTokenFormat = format.Object);
+ });
+
+ var client = new OpenIdConnectClient(server.CreateClient());
+
+ // Act
+ var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest
+ {
+ GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
+ Assert.Equal("The specified refresh token has already been redeemed.", response.ErrorDescription);
+
+ Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(mock => mock.IsRedeemedAsync(token, It.IsAny()), Times.Once());
+ }
+
[Fact]
public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsInvalid()
{
@@ -820,6 +883,9 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()))
.ReturnsAsync(token);
+ instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny()))
+ .ReturnsAsync(false);
+
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny()))
.ReturnsAsync(false);
});
@@ -926,7 +992,7 @@ namespace OpenIddict.Tests
}
[Fact]
- public async Task HandleTokenRequest_RefreshTokenIsAutomaticallyRedeemedWhenSlidingExpirationIsEnabled()
+ public async Task HandleTokenRequest_RefreshTokenIsAutomaticallyRedeemedWhenRollingTokensAreEnabled()
{
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
@@ -974,6 +1040,8 @@ namespace OpenIddict.Tests
builder.Services.AddSingleton(manager);
+ builder.UseRollingTokens();
+
builder.Configure(options => options.RefreshTokenFormat = format.Object);
});
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs
index 7401d32a..6af88080 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs
@@ -650,6 +650,9 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
+ instance.Setup(mock => mock.GetIdAsync(application, It.IsAny()))
+ .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
+
instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()))
.ReturnsAsync(true);
}));
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs
index 6599cfa1..fba55324 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs
@@ -46,8 +46,9 @@ namespace OpenIddict.Tests
Assert.NotNull(response.AccessToken);
Mock.Get(manager).Verify(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.AccessToken, "Bob le Magnifique",
- It.IsAny(), It.IsAny(),
+ It.Is(descriptor =>
+ descriptor.Subject == "Bob le Magnifique" &&
+ descriptor.Type == OpenIdConnectConstants.TokenTypeHints.AccessToken),
It.IsAny()), Times.Never());
}
@@ -57,16 +58,13 @@ namespace OpenIddict.Tests
// Arrange
var token = new OpenIddictToken
{
- End = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
- Start = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero),
+ CreationDate = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero),
+ ExpirationDate = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero)
};
var manager = CreateTokenManager(instance =>
{
- instance.Setup(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.AccessToken, "Bob le Magnifique",
- It.IsNotNull(), It.IsNotNull(),
- token.Start, token.End, It.IsAny()))
+ instance.Setup(mock => mock.CreateAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny()))
@@ -81,8 +79,8 @@ namespace OpenIddict.Tests
builder.Configure(options =>
{
- options.SystemClock = Mock.Of(mock => mock.UtcNow == token.Start.Value);
- options.AccessTokenLifetime = token.End.Value - token.Start.Value;
+ options.SystemClock = Mock.Of(mock => mock.UtcNow == token.CreationDate.Value);
+ options.AccessTokenLifetime = token.ExpirationDate.Value - token.CreationDate.Value;
});
});
@@ -101,10 +99,14 @@ namespace OpenIddict.Tests
Assert.NotNull(response.AccessToken);
Mock.Get(manager).Verify(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.AccessToken, "Bob le Magnifique",
- It.IsNotNull(), It.IsNotNull(),
- token.Start, token.End, It.IsAny()), Times.Once());
- Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny()), Times.Once());
+ It.Is(descriptor =>
+ descriptor.Ciphertext != null &&
+ descriptor.Hash != null &&
+ descriptor.ExpirationDate == token.ExpirationDate &&
+ descriptor.CreationDate == token.CreationDate &&
+ descriptor.Subject == "Bob le Magnifique" &&
+ descriptor.Type == OpenIdConnectConstants.TokenTypeHints.AccessToken),
+ It.IsAny()), Times.Once());
}
[Fact]
@@ -115,18 +117,11 @@ namespace OpenIddict.Tests
var manager = CreateTokenManager(instance =>
{
- instance.Setup(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.AccessToken, "Bob le Magnifique",
- It.IsNotNull(), It.IsNotNull(),
- It.IsAny(), It.IsAny(),
- It.IsAny()))
+ instance.Setup(mock => mock.CreateAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
-
- instance.Setup(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()))
- .Returns(Task.FromResult(0));
});
var server = CreateAuthorizationServer(builder =>
@@ -165,7 +160,12 @@ namespace OpenIddict.Tests
// Assert
Assert.NotNull(response.AccessToken);
- Mock.Get(manager).Verify(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(mock => mock.CreateAsync(
+ It.Is(descriptor =>
+ descriptor.ApplicationId == "3E228451-1555-46F7-A471-951EFBA23A56" &&
+ descriptor.Subject == "Bob le Magnifique" &&
+ descriptor.Type == OpenIdConnectConstants.TokenTypeHints.AccessToken),
+ It.IsAny()), Times.Once());
}
[Fact]
@@ -176,18 +176,11 @@ namespace OpenIddict.Tests
var manager = CreateTokenManager(instance =>
{
- instance.Setup(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.AccessToken, "Bob le Magnifique",
- It.IsNotNull(), It.IsNotNull(),
- It.IsAny(), It.IsAny(),
- It.IsAny()))
+ instance.Setup(mock => mock.CreateAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
-
- instance.Setup(mock => mock.SetAuthorizationAsync(token, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny()))
- .Returns(Task.FromResult(0));
});
var server = CreateAuthorizationServer(builder =>
@@ -218,7 +211,12 @@ namespace OpenIddict.Tests
// Assert
Assert.NotNull(response.AccessToken);
- Mock.Get(manager).Verify(mock => mock.SetAuthorizationAsync(token, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(mock => mock.CreateAsync(
+ It.Is(descriptor =>
+ descriptor.AuthorizationId == "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70" &&
+ descriptor.Subject == "Bob le Magnifique" &&
+ descriptor.Type == OpenIdConnectConstants.TokenTypeHints.AccessToken),
+ It.IsAny()), Times.Once());
}
[Fact]
@@ -267,8 +265,7 @@ namespace OpenIddict.Tests
Assert.NotNull(response.Code);
Mock.Get(manager).Verify(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
- It.IsAny(), It.IsAny(),
+ It.IsAny(),
It.IsAny()), Times.Never());
}
@@ -278,15 +275,13 @@ namespace OpenIddict.Tests
// Arrange
var token = new OpenIddictToken
{
- End = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero),
- Start = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
+ CreationDate = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
+ ExpirationDate = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero)
};
var manager = CreateTokenManager(instance =>
{
- instance.Setup(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
- token.Start, token.End, It.IsAny()))
+ instance.Setup(mock => mock.CreateAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny()))
@@ -310,14 +305,17 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
+
+ instance.Setup(mock => mock.GetIdAsync(application, It.IsAny()))
+ .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
}));
builder.Services.AddSingleton(manager);
builder.Configure(options =>
{
- options.SystemClock = Mock.Of(mock => mock.UtcNow == token.Start.Value);
- options.AuthorizationCodeLifetime = token.End.Value - token.Start.Value;
+ options.SystemClock = Mock.Of(mock => mock.UtcNow == token.CreationDate.Value);
+ options.AuthorizationCodeLifetime = token.ExpirationDate.Value - token.CreationDate.Value;
});
});
@@ -335,9 +333,14 @@ namespace OpenIddict.Tests
Assert.NotNull(response.Code);
Mock.Get(manager).Verify(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
- token.Start, token.End, It.IsAny()), Times.Once());
- Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny()), Times.Once());
+ It.Is(descriptor =>
+ descriptor.Ciphertext == null &&
+ descriptor.Hash == null &&
+ descriptor.ExpirationDate == token.ExpirationDate &&
+ descriptor.CreationDate == token.CreationDate &&
+ descriptor.Subject == "Bob le Magnifique" &&
+ descriptor.Type == OpenIdConnectConstants.TokenTypeHints.AuthorizationCode),
+ It.IsAny()), Times.Once());
}
[Fact]
@@ -346,16 +349,13 @@ namespace OpenIddict.Tests
// Arrange
var token = new OpenIddictToken
{
- End = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero),
- Start = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
+ CreationDate = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
+ ExpirationDate = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero)
};
var manager = CreateTokenManager(instance =>
{
- instance.Setup(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
- It.IsNotNull(), It.IsNotNull(),
- token.Start, token.End, It.IsAny()))
+ instance.Setup(mock => mock.CreateAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny()))
@@ -379,6 +379,9 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
+
+ instance.Setup(mock => mock.GetIdAsync(application, It.IsAny()))
+ .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
}));
builder.Services.AddSingleton(manager);
@@ -387,8 +390,8 @@ namespace OpenIddict.Tests
builder.Configure(options =>
{
- options.SystemClock = Mock.Of(mock => mock.UtcNow == token.Start.Value);
- options.AuthorizationCodeLifetime = token.End.Value - token.Start.Value;
+ options.SystemClock = Mock.Of(mock => mock.UtcNow == token.CreationDate.Value);
+ options.AuthorizationCodeLifetime = token.ExpirationDate.Value - token.CreationDate.Value;
});
});
@@ -406,10 +409,14 @@ namespace OpenIddict.Tests
Assert.NotNull(response.Code);
Mock.Get(manager).Verify(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
- It.IsNotNull(), It.IsNotNull(),
- token.Start, token.End, It.IsAny()), Times.Once());
- Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny()), Times.Once());
+ It.Is(descriptor =>
+ descriptor.Ciphertext != null &&
+ descriptor.Hash != null &&
+ descriptor.ExpirationDate == token.ExpirationDate &&
+ descriptor.CreationDate == token.CreationDate &&
+ descriptor.Subject == "Bob le Magnifique" &&
+ descriptor.Type == OpenIdConnectConstants.TokenTypeHints.AuthorizationCode),
+ It.IsAny()), Times.Once());
}
[Fact]
@@ -420,17 +427,11 @@ namespace OpenIddict.Tests
var manager = CreateTokenManager(instance =>
{
- instance.Setup(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
- It.IsAny(), It.IsAny(),
- It.IsAny()))
+ instance.Setup(mock => mock.CreateAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
-
- instance.Setup(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()))
- .Returns(Task.FromResult(0));
});
var server = CreateAuthorizationServer(builder =>
@@ -471,7 +472,12 @@ namespace OpenIddict.Tests
// Assert
Assert.NotNull(response.Code);
- Mock.Get(manager).Verify(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(mock => mock.CreateAsync(
+ It.Is(descriptor =>
+ descriptor.ApplicationId == "3E228451-1555-46F7-A471-951EFBA23A56" &&
+ descriptor.Subject == "Bob le Magnifique" &&
+ descriptor.Type == OpenIdConnectConstants.TokenTypeHints.AuthorizationCode),
+ It.IsAny()), Times.Once());
}
[Fact]
@@ -482,20 +488,11 @@ namespace OpenIddict.Tests
var manager = CreateTokenManager(instance =>
{
- instance.Setup(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
- It.IsAny(), It.IsAny(),
- It.IsAny()))
+ instance.Setup(mock => mock.CreateAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
-
- instance.Setup(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()))
- .Returns(Task.FromResult(0));
-
- instance.Setup(mock => mock.SetAuthorizationAsync(token, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny()))
- .Returns(Task.FromResult(0));
});
var server = CreateAuthorizationServer(builder =>
@@ -543,7 +540,13 @@ namespace OpenIddict.Tests
// Assert
Assert.NotNull(response.Code);
- Mock.Get(manager).Verify(mock => mock.SetAuthorizationAsync(token, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(mock => mock.CreateAsync(
+ It.Is(descriptor =>
+ descriptor.ApplicationId == "3E228451-1555-46F7-A471-951EFBA23A56" &&
+ descriptor.AuthorizationId == "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70" &&
+ descriptor.Subject == "Bob le Magnifique" &&
+ descriptor.Type == OpenIdConnectConstants.TokenTypeHints.AuthorizationCode),
+ It.IsAny()), Times.Once());
}
[Fact]
@@ -582,11 +585,8 @@ namespace OpenIddict.Tests
builder.Services.AddSingleton(CreateTokenManager(instance =>
{
- instance.Setup(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
- It.IsAny(), It.IsAny(),
- It.IsAny()))
- .ReturnsAsync(token);
+ instance.Setup(mock => mock.CreateAsync(It.IsAny(), It.IsAny()))
+ .ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
@@ -608,8 +608,61 @@ namespace OpenIddict.Tests
// Assert
Assert.NotNull(response.Code);
- Mock.Get(manager).Verify(mock => mock.CreateAsync("Bob le Magnifique", "3E228451-1555-46F7-A471-951EFBA23A56",
- It.IsAny>(), It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(mock => mock.CreateAsync(
+ It.Is(descriptor =>
+ descriptor.ApplicationId == "3E228451-1555-46F7-A471-951EFBA23A56" &&
+ descriptor.Subject == "Bob le Magnifique"),
+ It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task SerializeRefreshToken_ExtendsLifetimeWhenRollingTokensAreDisabled()
+ {
+ // Arrange
+ var token = new OpenIddictToken
+ {
+ CreationDate = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
+ ExpirationDate = new DateTimeOffset(2017, 01, 10, 00, 00, 00, TimeSpan.Zero)
+ };
+
+ var manager = CreateTokenManager(instance =>
+ {
+ instance.Setup(mock => mock.FindByHashAsync("d80c119138b3aaeefce94093032c0204c547dc27cc5fe97f32933becd48b7bf5", It.IsAny()))
+ .ReturnsAsync(token);
+
+ instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()))
+ .ReturnsAsync(token);
+ });
+
+ var server = CreateAuthorizationServer(builder =>
+ {
+ builder.Services.AddSingleton(manager);
+
+ builder.UseReferenceTokens();
+
+ builder.Configure(options =>
+ {
+ options.SystemClock = Mock.Of(mock => mock.UtcNow ==
+ new DateTimeOffset(2017, 01, 05, 00, 00, 00, TimeSpan.Zero));
+ options.RefreshTokenLifetime = TimeSpan.FromDays(10);
+ });
+ });
+
+ var client = new OpenIdConnectClient(server.CreateClient());
+
+ // Act
+ var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest
+ {
+ GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken,
+ RefreshToken = "3E228451-1555-46F7-A471-951EFBA23A56"
+ });
+
+ // Assert
+ Assert.Null(response.RefreshToken);
+
+ Mock.Get(manager).Verify(mock => mock.ExtendAsync(token,
+ new DateTimeOffset(2017, 01, 15, 00, 00, 00, TimeSpan.Zero),
+ It.IsAny()), Times.Never());
}
[Fact]
@@ -642,8 +695,7 @@ namespace OpenIddict.Tests
Assert.NotNull(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique",
- It.IsAny(), It.IsAny(),
+ It.IsAny(),
It.IsAny()), Times.Never());
}
@@ -653,15 +705,13 @@ namespace OpenIddict.Tests
// Arrange
var token = new OpenIddictToken
{
- End = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero),
- Start = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero)
+ CreationDate = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
+ ExpirationDate = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero)
};
var manager = CreateTokenManager(instance =>
{
- instance.Setup(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique",
- token.Start, token.End, It.IsAny()))
+ instance.Setup(mock => mock.CreateAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny()))
@@ -674,8 +724,8 @@ namespace OpenIddict.Tests
builder.Configure(options =>
{
- options.SystemClock = Mock.Of(mock => mock.UtcNow == token.Start.Value);
- options.RefreshTokenLifetime = token.End.Value - token.Start.Value;
+ options.SystemClock = Mock.Of(mock => mock.UtcNow == token.CreationDate.Value);
+ options.RefreshTokenLifetime = token.ExpirationDate.Value - token.CreationDate.Value;
});
});
@@ -694,9 +744,14 @@ namespace OpenIddict.Tests
Assert.NotNull(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique",
- token.Start, token.End, It.IsAny()), Times.Once());
- Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny()), Times.Once());
+ It.Is(descriptor =>
+ descriptor.Ciphertext == null &&
+ descriptor.Hash == null &&
+ descriptor.ExpirationDate == token.ExpirationDate &&
+ descriptor.CreationDate == token.CreationDate &&
+ descriptor.Subject == "Bob le Magnifique" &&
+ descriptor.Type == OpenIdConnectConstants.TokenTypeHints.RefreshToken),
+ It.IsAny()), Times.Once());
}
[Fact]
@@ -705,16 +760,13 @@ namespace OpenIddict.Tests
// Arrange
var token = new OpenIddictToken
{
- End = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero),
- Start = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
+ CreationDate = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
+ ExpirationDate = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero)
};
var manager = CreateTokenManager(instance =>
{
- instance.Setup(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique",
- It.IsNotNull(), It.IsNotNull(),
- token.Start, token.End, It.IsAny()))
+ instance.Setup(mock => mock.CreateAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny()))
@@ -729,8 +781,8 @@ namespace OpenIddict.Tests
builder.Configure(options =>
{
- options.SystemClock = Mock.Of(mock => mock.UtcNow == token.Start.Value);
- options.RefreshTokenLifetime = token.End.Value - token.Start.Value;
+ options.SystemClock = Mock.Of(mock => mock.UtcNow == token.CreationDate.Value);
+ options.RefreshTokenLifetime = token.ExpirationDate.Value - token.CreationDate.Value;
});
});
@@ -749,10 +801,14 @@ namespace OpenIddict.Tests
Assert.NotNull(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique",
- It.IsNotNull(), It.IsNotNull(),
- token.Start, token.End, It.IsAny()), Times.Once());
- Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny()), Times.Once());
+ It.Is(descriptor =>
+ descriptor.Ciphertext != null &&
+ descriptor.Hash != null &&
+ descriptor.ExpirationDate == token.ExpirationDate &&
+ descriptor.CreationDate == token.CreationDate &&
+ descriptor.Subject == "Bob le Magnifique" &&
+ descriptor.Type == OpenIdConnectConstants.TokenTypeHints.RefreshToken),
+ It.IsAny()), Times.Once());
}
[Fact]
@@ -763,17 +819,11 @@ namespace OpenIddict.Tests
var manager = CreateTokenManager(instance =>
{
- instance.Setup(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique",
- It.IsAny(), It.IsAny(),
- It.IsAny()))
+ instance.Setup(mock => mock.CreateAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
-
- instance.Setup(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()))
- .Returns(Task.FromResult(0));
});
var server = CreateAuthorizationServer(builder =>
@@ -810,7 +860,12 @@ namespace OpenIddict.Tests
// Assert
Assert.NotNull(response.RefreshToken);
- Mock.Get(manager).Verify(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(mock => mock.CreateAsync(
+ It.Is(descriptor =>
+ descriptor.ApplicationId == "3E228451-1555-46F7-A471-951EFBA23A56" &&
+ descriptor.Subject == "Bob le Magnifique" &&
+ descriptor.Type == OpenIdConnectConstants.TokenTypeHints.RefreshToken),
+ It.IsAny()), Times.Once());
}
[Fact]
@@ -821,17 +876,11 @@ namespace OpenIddict.Tests
var manager = CreateTokenManager(instance =>
{
- instance.Setup(mock => mock.CreateAsync(
- OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique",
- It.IsAny(), It.IsAny(),
- It.IsAny()))
+ instance.Setup(mock => mock.CreateAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
-
- instance.Setup(mock => mock.SetAuthorizationAsync(token, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny()))
- .Returns(Task.FromResult(0));
});
var server = CreateAuthorizationServer(builder =>
@@ -860,7 +909,12 @@ namespace OpenIddict.Tests
// Assert
Assert.NotNull(response.RefreshToken);
- Mock.Get(manager).Verify(mock => mock.SetAuthorizationAsync(token, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(mock => mock.CreateAsync(
+ It.Is(descriptor =>
+ descriptor.AuthorizationId == "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70" &&
+ descriptor.Subject == "Bob le Magnifique" &&
+ descriptor.Type == OpenIdConnectConstants.TokenTypeHints.RefreshToken),
+ It.IsAny()), Times.Once());
}
}
}