diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.cs b/src/OpenIddict.Server/OpenIddictServerEvents.cs
index dc194131..99133dae 100644
--- a/src/OpenIddict.Server/OpenIddictServerEvents.cs
+++ b/src/OpenIddict.Server/OpenIddictServerEvents.cs
@@ -247,17 +247,6 @@ namespace OpenIddict.Server
/// Gets the client identifier, or null if the client application is unknown.
///
public string ClientId => Request.ClientId;
-
- ///
- /// Gets a boolean indicating whether the
- /// method was called.
- ///
- public bool IsHandled { get; private set; }
-
- ///
- /// Marks the authentication process as handled by the application code.
- ///
- public void HandleAuthentication() => IsHandled = true;
}
///
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
index cc7c785b..33daed0c 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
@@ -301,7 +301,7 @@ namespace OpenIddict.Server
else if (@event.IsRejected)
{
context.Reject(
- error: @event.Error ?? Errors.InvalidGrant,
+ error: @event.Error ?? Errors.InvalidRequest,
description: @event.ErrorDescription,
uri: @event.ErrorUri);
return;
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
index 3c086556..e9040dad 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
@@ -299,7 +299,7 @@ namespace OpenIddict.Server
else if (@event.IsRejected)
{
context.Reject(
- error: @event.Error ?? Errors.InvalidGrant,
+ error: @event.Error ?? Errors.InvalidRequest,
description: @event.ErrorDescription,
uri: @event.ErrorUri);
return;
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
index 0f46d385..82c39261 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
@@ -55,7 +55,7 @@ namespace OpenIddict.Server
/*
* Sign-in processing:
*/
- ValidateSigninDemand.Descriptor,
+ ValidateSignInDemand.Descriptor,
RestoreInternalClaims.Descriptor,
AttachDefaultScopes.Descriptor,
AttachDefaultPresenters.Descriptor,
@@ -156,7 +156,6 @@ namespace OpenIddict.Server
.AppendLine("to validate a token for an invalid grant type (e.g password).")
.ToString());
-
default: throw new InvalidOperationException(new StringBuilder()
.AppendLine("An identity cannot be extracted from this request.")
.Append("This generally indicates that the OpenIddict server stack was asked ")
@@ -1221,14 +1220,14 @@ namespace OpenIddict.Server
/// Contains the logic responsible of ensuring that the sign-in demand
/// is compatible with the type of the endpoint that handled the request.
///
- public class ValidateSigninDemand : IOpenIddictServerHandler
+ public class ValidateSignInDemand : IOpenIddictServerHandler
{
///
/// Gets the default descriptor definition assigned to this handler.
///
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
+ .UseSingletonHandler()
.SetOrder(int.MinValue + 100_000)
.Build();
@@ -1303,7 +1302,7 @@ namespace OpenIddict.Server
if (string.IsNullOrEmpty(context.Principal.GetClaim(Claims.Subject)))
{
throw new InvalidOperationException(new StringBuilder()
- .AppendLine("The specified principal was rejected because the mandatory subject claim was missing.")
+ .Append("The specified principal was rejected because the mandatory subject claim was missing.")
.ToString());
}
@@ -1322,7 +1321,7 @@ namespace OpenIddict.Server
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(ValidateSigninDemand.Descriptor.Order + 1_000)
+ .SetOrder(ValidateSignInDemand.Descriptor.Order + 1_000)
.Build();
///
@@ -1754,6 +1753,13 @@ namespace OpenIddict.Server
return true;
}
+ // Never exclude the presenters and scope private claims.
+ if (string.Equals(claim.Type, Claims.Private.Presenters, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(claim.Type, Claims.Private.Scopes, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
// Never include the public or internal token identifiers to ensure the identifiers
// that are automatically inherited from the parent token are not reused for the new token.
if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) ||
@@ -1807,22 +1813,13 @@ namespace OpenIddict.Server
// Set the public audiences collection using the private resource claims stored in the principal.
principal.SetAudiences(context.Principal.GetResources());
- // Set the authorized party using the first presenters (typically the client identifier), if available.
- principal.SetClaim(Claims.AuthorizedParty, context.Principal.GetPresenters().FirstOrDefault());
-
- // Set the public scope claim using the private scope claims from the principal.
- // Note: scopes are deliberately formatted as a single space-separated
- // string to respect the usual representation of the standard scope claim.
- // See https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-02.
- principal.SetClaim(Claims.Scope, string.Join(" ", context.Principal.GetScopes()));
-
// When receiving a grant_type=refresh_token request, determine whether the client application
// requests a limited set of scopes and immediately replace the scopes collection if necessary.
if (context.EndpointType == OpenIddictServerEndpointType.Token &&
context.Request.IsRefreshTokenGrantType() && !string.IsNullOrEmpty(context.Request.Scope))
{
var scopes = context.Request.GetScopes();
- principal.SetClaim(Claims.Scope, string.Join(" ", scopes.Intersect(context.Principal.GetScopes())));
+ principal.SetScopes(scopes.Intersect(context.Principal.GetScopes()));
context.Logger.LogDebug("The access token scopes will be limited to the scopes " +
"requested by the client application: {Scopes}.", scopes);
@@ -2695,6 +2692,26 @@ namespace OpenIddict.Server
return default;
}
+ // Copy the principal and exclude the presenters/scopes private claims,
+ // that are manually mapped to public standard azp/scope JWT claims.
+ var principal = context.AccessTokenPrincipal.Clone(claim => claim.Type switch
+ {
+ Claims.Private.Presenters => false,
+ Claims.Private.Scopes => false,
+ Claims.Private.TokenId => false,
+
+ _ => true
+ });
+
+ // Set the authorized party using the first presenters (typically the client identifier), if available.
+ principal.SetClaim(Claims.AuthorizedParty, context.AccessTokenPrincipal.GetPresenters().FirstOrDefault());
+
+ // Set the public scope claim using the private scope claims from the principal.
+ // Note: scopes are deliberately formatted as a single space-separated
+ // string to respect the usual representation of the standard scope claim.
+ // See https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-02.
+ principal.SetClaim(Claims.Scope, string.Join(" ", context.AccessTokenPrincipal.GetScopes()));
+
var token = context.Options.JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor
{
AdditionalHeaderClaims = new Dictionary(StringComparer.Ordinal)
@@ -2704,7 +2721,7 @@ namespace OpenIddict.Server
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(credentials =>
credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First(),
- Subject = (ClaimsIdentity) context.AccessTokenPrincipal.Identity
+ Subject = (ClaimsIdentity) principal.Identity
});
var credentials = context.Options.EncryptionCredentials.FirstOrDefault(
@@ -2723,7 +2740,7 @@ namespace OpenIddict.Server
context.Logger.LogTrace("The access token '{Identifier}' was successfully created: {Payload}. " +
"The principal used to create the token contained the following claims: {Claims}.",
context.AccessTokenPrincipal.GetClaim(Claims.JwtId),
- context.Response.AccessToken, context.AccessTokenPrincipal.Claims);
+ context.Response.AccessToken, principal.Claims);
return default;
}
diff --git a/src/OpenIddict.Server/OpenIddictServerProvider.cs b/src/OpenIddict.Server/OpenIddictServerProvider.cs
index dcd8b388..7911b25d 100644
--- a/src/OpenIddict.Server/OpenIddictServerProvider.cs
+++ b/src/OpenIddict.Server/OpenIddictServerProvider.cs
@@ -67,10 +67,6 @@ namespace OpenIddict.Server
_logger.LogDebug("The request was rejected in user code.");
return;
- case BaseValidatingTicketContext notification when notification.IsHandled:
- _logger.LogDebug("Authentication was handled in user code.");
- return;
-
default: continue;
}
}
diff --git a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs
index 04c61260..1590ed22 100644
--- a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs
+++ b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs
@@ -1362,7 +1362,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var identity = (ClaimsIdentity) null;
// Act and assert
- var exception = Assert.Throws(() => identity.Clone(c => true));
+ var exception = Assert.Throws(() => identity.Clone(claim => true));
Assert.Equal("identity", exception.ParamName);
}
@@ -1375,7 +1375,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
identity.AddClaim("type", "value");
// Act
- var copy = identity.Clone(c => true);
+ var copy = identity.Clone(claim => true);
// Assert
Assert.Equal("value", copy.GetClaim("type"));
@@ -1389,7 +1389,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var principal = (ClaimsPrincipal) null;
// Act and assert
- var exception = Assert.Throws(() => principal.Clone(c => true));
+ var exception = Assert.Throws(() => principal.Clone(claim => true));
Assert.Equal("principal", exception.ParamName);
}
@@ -1404,7 +1404,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var principal = new ClaimsPrincipal(identity);
// Act
- var copy = principal.Clone(c => true);
+ var copy = principal.Clone(claim => true);
// Assert
Assert.Equal("Bob le Bricoleur", copy.GetClaim(Claims.Name));
@@ -1419,7 +1419,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
identity.AddClaim("type", "value");
// Act
- var copy = identity.Clone(c => true);
+ var copy = identity.Clone(claim => true);
copy.AddClaim("clone_type", "value");
// Assert
@@ -1437,7 +1437,7 @@ namespace OpenIddict.Abstractions.Tests.Primitives
var principal = new ClaimsPrincipal(identity);
// Act
- var copy = principal.Clone(c => true);
+ var copy = principal.Clone(claim => true);
copy.SetClaim("clone_claim", "value");
// Assert
diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
index b70ca52e..45dc2836 100644
--- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
+++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
@@ -300,7 +300,8 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
- .SetExpirationDate(DateTimeOffset.UtcNow - TimeSpan.FromDays(1));
+ .SetExpirationDate(DateTimeOffset.UtcNow - TimeSpan.FromDays(1))
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -338,7 +339,8 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
- .SetExpirationDate(DateTimeOffset.UtcNow - TimeSpan.FromDays(1));
+ .SetExpirationDate(DateTimeOffset.UtcNow - TimeSpan.FromDays(1))
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -375,7 +377,8 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
- .SetPresenters(Enumerable.Empty());
+ .SetPresenters(Enumerable.Empty())
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -414,7 +417,8 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
- .SetPresenters("Contoso");
+ .SetPresenters("Contoso")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -452,7 +456,8 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
- .SetPresenters("Contoso");
+ .SetPresenters("Contoso")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -491,6 +496,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur")
.SetClaim(Claims.Private.RedirectUri, "http://www.fabrikam.com/callback");
return default;
@@ -531,6 +537,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur")
.SetClaim(Claims.Private.RedirectUri, "http://www.fabrikam.com/callback");
return default;
@@ -572,6 +579,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur")
.SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM")
.SetClaim(Claims.Private.CodeChallengeMethod, CodeChallengeMethods.Sha256);
@@ -613,6 +621,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur")
.SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM")
.SetClaim(Claims.Private.CodeChallengeMethod, null);
@@ -655,6 +664,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur")
.SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM")
.SetClaim(Claims.Private.CodeChallengeMethod, "custom_code_challenge_method");
@@ -699,6 +709,7 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur")
.SetClaim(Claims.Private.CodeChallenge, challenge)
.SetClaim(Claims.Private.CodeChallengeMethod, method);
@@ -741,8 +752,8 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
- .SetClaim(Claims.Subject, "Bob le Magnifique")
.SetPresenters("Fabrikam")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur")
.SetClaim(Claims.Private.CodeChallenge, challenge)
.SetClaim(Claims.Private.CodeChallengeMethod, method);
@@ -783,7 +794,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
- .SetScopes(Enumerable.Empty());
+ .SetScopes(Enumerable.Empty())
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -823,7 +835,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
- .SetScopes("profile", "email");
+ .SetScopes("profile", "email")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -862,7 +875,8 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
- .SetScopes(Enumerable.Empty());
+ .SetScopes(Enumerable.Empty())
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -900,7 +914,8 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
- .SetScopes("profile", "email");
+ .SetScopes("profile", "email")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -1640,7 +1655,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
- .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
+ .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -1691,7 +1707,8 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
- .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
+ .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -1735,7 +1752,8 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
- .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
+ .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -1794,7 +1812,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
- .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
+ .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -1848,7 +1867,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
- .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
+ .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -1915,7 +1935,8 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
- .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
+ .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -1965,7 +1986,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
- .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
+ .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -2047,7 +2069,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
- .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
+ .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -2138,7 +2161,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
- .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
+ .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -2225,7 +2249,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
- .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
+ .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -2287,7 +2312,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
- .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
+ .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -2360,7 +2386,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
- .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
+ .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -2408,7 +2435,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
- .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
+ .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -2492,7 +2520,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
- .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
+ .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -2560,7 +2589,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
- .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
+ .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -2645,7 +2675,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetPresenters("Fabrikam")
.SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
- .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
+ .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -2725,7 +2756,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
- .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
+ .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -2796,7 +2828,8 @@ namespace OpenIddict.Server.FunctionalTests
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
- .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
+ .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
@@ -2878,9 +2911,9 @@ namespace OpenIddict.Server.FunctionalTests
builder.UseInlineHandler(context =>
{
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
- .SetClaim(Claims.Subject, "Bob le Bricoleur")
.SetPresenters("Fabrikam")
- .SetInternalTokenId("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC");
+ .SetInternalTokenId("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
if (context.Request.IsAuthorizationCodeGrantType())
{
diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs
index 33feb6c8..f8e4ec23 100644
--- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs
+++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs
@@ -5,8 +5,11 @@
*/
using System;
+using System.Collections.Immutable;
+using System.Linq;
using System.Security.Claims;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -423,6 +426,2414 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Equal("Bob le Magnifique", (string) response[Claims.Subject]);
}
+ [Fact]
+ public async Task ProcessSignIn_UnknownEndpointCausesAnException()
+ {
+ // Arrange
+ var client = CreateClient();
+
+ // Act and assert
+ var exception = await Assert.ThrowsAsync(delegate
+ {
+ return client.PostAsync("/signin", new OpenIddictRequest());
+ });
+
+ Assert.Equal("An OpenID Connect response cannot be returned from this endpoint.", exception.Message);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_InvalidEndpointCausesAnException()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+ options.SetConfigurationEndpointUris("/signin");
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ context.SkipRequest();
+
+ return default;
+ }));
+ });
+
+ // Act and assert
+ var exception = await Assert.ThrowsAsync(delegate
+ {
+ return client.GetAsync("/signin");
+ });
+
+ Assert.Equal("An OpenID Connect response cannot be returned from this endpoint.", exception.Message);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_NullIdentityCausesAnException()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ context.Principal = new ClaimsPrincipal();
+
+ return default;
+ }));
+ });
+
+ // Act and assert
+ var exception = await Assert.ThrowsAsync(delegate
+ {
+ return client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ });
+ });
+
+ Assert.Equal(new StringBuilder()
+ .AppendLine("The specified principal doesn't contain any claims-based identity.")
+ .Append("Make sure that both 'ClaimsPrincipal.Identity' is not null.")
+ .ToString(), exception.Message);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_NullAuthenticationTypeCausesAnException()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity());
+
+ return default;
+ }));
+ });
+
+ // Act and assert
+ var exception = await Assert.ThrowsAsync(delegate
+ {
+ return client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ });
+ });
+
+ Assert.Equal(new StringBuilder()
+ .AppendLine("The specified principal doesn't contain a valid/authenticated identity.")
+ .Append("Make sure that 'ClaimsPrincipal.Identity.AuthenticationType' is not null ")
+ .Append("and that 'ClaimsPrincipal.Identity.IsAuthenticated' returns 'true'.")
+ .ToString(), exception.Message);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_MissingSubjectCausesAnException()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"));
+
+ return default;
+ }));
+ });
+
+ // Act and assert
+ var exception = await Assert.ThrowsAsync(delegate
+ {
+ return client.PostAsync("/connect/authorize", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ RedirectUri = "http://www.fabrikam.com/path",
+ ResponseType = ResponseTypes.Code
+ });
+ });
+
+ Assert.Equal("The specified principal was rejected because the mandatory subject claim was missing.", exception.Message);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_ScopeDefaultsToOpenId()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal(new[] { Scopes.OpenId }, context.Principal.GetScopes());
+
+ return default;
+ }));
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w",
+ Scope = Scopes.OpenId
+ });
+
+ // Assert
+ Assert.NotNull(response.AccessToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_ResourcesAreInferredFromAudiences()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal(new[] { "http://www.fabrikam.com/" }, context.Principal.GetResources());
+
+ return default;
+ }));
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetAudiences("http://www.fabrikam.com/")
+ .SetScopes(Scopes.OfflineAccess)
+ .SetClaim(Claims.Subject, "Bob le Magnifique");
+
+ return default;
+ }));
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ });
+
+ // Assert
+ Assert.NotNull(response.AccessToken);
+ Assert.NotNull(response.RefreshToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_AllowsOverridingDefaultTokensSelection()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ context.IncludeAccessToken = false;
+ context.IncludeAuthorizationCode = true;
+ context.IncludeIdentityToken = true;
+ context.IncludeRefreshToken = true;
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ });
+
+ // Assert
+ Assert.Null(response.AccessToken);
+ Assert.NotNull(response.Code);
+ Assert.NotNull(response.IdToken);
+ Assert.NotNull(response.RefreshToken);
+ }
+
+ [Theory]
+ [InlineData("code")]
+ [InlineData("code id_token")]
+ [InlineData("code id_token token")]
+ [InlineData("code token")]
+ public async Task ProcessSignIn_AnAuthorizationCodeIsReturnedForCodeAndHybridFlowRequests(string type)
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.True(context.IncludeAuthorizationCode);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ Nonce = "n-0S6_WzA2Mj",
+ RedirectUri = "http://www.fabrikam.com/path",
+ ResponseType = type,
+ Scope = Scopes.OpenId
+ });
+
+ // Assert
+ Assert.NotNull(response.Code);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_ScopesCanBeOverridenForRefreshTokenRequests()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+ options.RegisterScopes(Scopes.Profile);
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.Profile, Scopes.OfflineAccess)
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal(new[] { Scopes.Profile }, context.AccessTokenPrincipal.GetScopes());
+
+ return default;
+ });
+
+ builder.SetOrder(PrepareAccessTokenPrincipal.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8",
+ Scope = Scopes.Profile
+ });
+
+ // Assert
+ Assert.NotNull(response.AccessToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_ScopesAreReturnedWhenTheyDifferFromRequestedScopes()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+ options.RegisterScopes(Scopes.Phone, Scopes.Profile);
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.Profile)
+ .SetClaim(Claims.Subject, "Bob le Magnifique");
+
+ return default;
+ }));
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w",
+ Scope = "openid phone profile"
+ });
+
+ // Assert
+ Assert.Equal(Scopes.Profile, response.Scope);
+ }
+
+ [Theory]
+ [InlineData("code id_token token")]
+ [InlineData("code token")]
+ [InlineData("id_token token")]
+ [InlineData("token")]
+ public async Task ProcessSignIn_AnAccessTokenIsReturnedForImplicitAndHybridFlowRequests(string type)
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.True(context.IncludeAccessToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ Nonce = "n-0S6_WzA2Mj",
+ RedirectUri = "http://www.fabrikam.com/path",
+ ResponseType = type,
+ Scope = Scopes.OpenId
+ });
+
+ // Assert
+ Assert.NotNull(response.AccessToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_AnAccessTokenIsReturnedForCodeGrantRequests()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
+ Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetPresenters("Fabrikam")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.True(context.IncludeAccessToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ Code = "SplxlOBeZQQYbYS6WxSbIA",
+ GrantType = GrantTypes.AuthorizationCode
+ });
+
+ // Assert
+ Assert.NotNull(response.AccessToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_AnAccessTokenIsReturnedForRefreshTokenGrantRequests()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.True(context.IncludeAccessToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.NotNull(response.AccessToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_AnAccessTokenIsReturnedForPasswordGrantRequests()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.True(context.IncludeAccessToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ });
+
+ // Assert
+ Assert.NotNull(response.AccessToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_AnAccessTokenIsReturnedForClientCredentialsGrantRequests()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.True(context.IncludeAccessToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
+ GrantType = GrantTypes.ClientCredentials,
+ });
+
+ // Assert
+ Assert.NotNull(response.AccessToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_AnAccessTokenIsReturnedForCustomGrantRequests()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+ options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant");
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.True(context.IncludeAccessToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = "urn:ietf:params:oauth:grant-type:custom_grant"
+ });
+
+ // Assert
+ Assert.NotNull(response.AccessToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_ExpiresInIsReturnedWhenExpirationDateIsKnown()
+ {
+ // Arrange
+ var client = CreateClient(options => options.EnableDegradedMode());
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ });
+
+ // Assert
+ Assert.NotNull(response.ExpiresIn);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_NoRefreshTokenIsReturnedWhenOfflineAccessScopeIsNotGranted()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.False(context.IncludeRefreshToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ });
+
+ // Assert
+ Assert.Null(response.RefreshToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_ARefreshTokenIsReturnedForCodeGrantRequests()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
+ Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetPresenters("Fabrikam")
+ .SetScopes(Scopes.OfflineAccess)
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.True(context.IncludeRefreshToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ Code = "SplxlOBeZQQYbYS6WxSbIA",
+ GrantType = GrantTypes.AuthorizationCode
+ });
+
+ // Assert
+ Assert.NotNull(response.RefreshToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_NoRefreshTokenIsReturnedForRefreshTokenGrantRequests()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OfflineAccess)
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.False(context.IncludeRefreshToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.Null(response.RefreshToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_NoRefreshTokenIsReturnedWhenSlidingExpirationIsDisabled()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+ options.DisableSlidingExpiration();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OfflineAccess)
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.False(context.IncludeRefreshToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.Null(response.RefreshToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_ARefreshTokenIsReturnedForPasswordGrantRequests()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.True(context.IncludeRefreshToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OfflineAccess)
+ .SetClaim(Claims.Subject, "Bob le Magnifique");
+
+ return default;
+ }));
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ });
+
+ // Assert
+ Assert.NotNull(response.RefreshToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_ARefreshTokenIsReturnedForClientCredentialsGrantRequests()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.True(context.IncludeRefreshToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OfflineAccess)
+ .SetClaim(Claims.Subject, "Bob le Magnifique");
+
+ return default;
+ }));
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
+ GrantType = GrantTypes.ClientCredentials,
+ });
+
+ // Assert
+ Assert.NotNull(response.RefreshToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_ARefreshTokenIsReturnedForCustomGrantRequests()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+ options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant");
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.True(context.IncludeRefreshToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OfflineAccess)
+ .SetClaim(Claims.Subject, "Bob le Magnifique");
+
+ return default;
+ }));
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = "urn:ietf:params:oauth:grant-type:custom_grant"
+ });
+
+ // Assert
+ Assert.NotNull(response.RefreshToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_NoIdentityTokenIsReturnedWhenOfflineAccessScopeIsNotGranted()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.False(context.IncludeIdentityToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ });
+
+ // Assert
+ Assert.Null(response.IdToken);
+ }
+
+ [Theory]
+ [InlineData("code id_token")]
+ [InlineData("code id_token token")]
+ [InlineData("id_token")]
+ [InlineData("id_token token")]
+ public async Task ProcessSignIn_AnIdentityTokenIsReturnedForImplicitAndHybridFlowRequests(string type)
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.True(context.IncludeIdentityToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ Nonce = "n-0S6_WzA2Mj",
+ RedirectUri = "http://www.fabrikam.com/path",
+ ResponseType = type,
+ Scope = Scopes.OpenId
+ });
+
+ // Assert
+ Assert.NotNull(response.IdToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_AnIdentityTokenIsReturnedForCodeGrantRequests()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
+ Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetPresenters("Fabrikam")
+ .SetScopes(Scopes.OpenId)
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.True(context.IncludeIdentityToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ Code = "SplxlOBeZQQYbYS6WxSbIA",
+ GrantType = GrantTypes.AuthorizationCode
+ });
+
+ // Assert
+ Assert.NotNull(response.IdToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_AnIdentityTokenIsReturnedForRefreshTokenGrantRequests()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OpenId)
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.True(context.IncludeIdentityToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.NotNull(response.IdToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_AnIdentityTokenIsReturnedForPasswordGrantRequests()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.True(context.IncludeIdentityToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w",
+ Scope = Scopes.OpenId
+ });
+
+ // Assert
+ Assert.NotNull(response.IdToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_AnIdentityTokenIsReturnedForClientCredentialsGrantRequests()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.True(context.IncludeIdentityToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
+ GrantType = GrantTypes.ClientCredentials,
+ Scope = Scopes.OpenId
+ });
+
+ // Assert
+ Assert.NotNull(response.IdToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_AnIdentityTokenIsReturnedForCustomGrantRequests()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+ options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant");
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.True(context.IncludeIdentityToken);
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateReturnedTokens.Descriptor.Order + 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = "urn:ietf:params:oauth:grant-type:custom_grant",
+ Scope = Scopes.OpenId
+ });
+
+ // Assert
+ Assert.NotNull(response.IdToken);
+ }
+
+ [Theory]
+ [InlineData("custom_error", null, null)]
+ [InlineData("custom_error", "custom_description", null)]
+ [InlineData("custom_error", "custom_description", "custom_uri")]
+ [InlineData(null, "custom_description", null)]
+ [InlineData(null, "custom_description", "custom_uri")]
+ [InlineData(null, null, "custom_uri")]
+ [InlineData(null, null, null)]
+ public async Task ProcessSignIn_AllowsRejectingAuthorizationRequest(string error, string description, string uri)
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ context.Reject(error, description, uri);
+
+ return default;
+ }));
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ RedirectUri = "http://www.fabrikam.com/path",
+ ResponseType = ResponseTypes.Code,
+ Scope = Scopes.OpenId
+ });
+
+ // Assert
+ Assert.Equal(error ?? Errors.InvalidRequest, response.Error);
+ Assert.Equal(description, response.ErrorDescription);
+ Assert.Equal(uri, response.ErrorUri);
+ }
+
+ [Theory]
+ [InlineData("custom_error", null, null)]
+ [InlineData("custom_error", "custom_description", null)]
+ [InlineData("custom_error", "custom_description", "custom_uri")]
+ [InlineData(null, "custom_description", null)]
+ [InlineData(null, "custom_description", "custom_uri")]
+ [InlineData(null, null, "custom_uri")]
+ [InlineData(null, null, null)]
+ public async Task ProcessSignIn_AllowsRejectingTokenRequest(string error, string description, string uri)
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ context.Reject(error, description, uri);
+
+ return default;
+ }));
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ });
+
+ // Assert
+ Assert.Equal(error ?? Errors.InvalidRequest, response.Error);
+ Assert.Equal(description, response.ErrorDescription);
+ Assert.Equal(uri, response.ErrorUri);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_AllowsHandlingResponse()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ context.Transaction.SetProperty("custom_response", new
+ {
+ name = "Bob le Bricoleur"
+ });
+
+ context.HandleRequest();
+
+ return default;
+ }));
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ });
+
+ // Assert
+ Assert.Equal("Bob le Bricoleur", (string) response["name"]);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_PrivateClaimsAreAutomaticallyRestored()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+ options.UseRollingTokens();
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal(new[] { Scopes.OpenId, Scopes.OfflineAccess }, context.Principal.GetScopes());
+ Assert.Equal("value", context.Principal.GetClaim(Claims.Prefixes.Private + "_private_claim"));
+
+ return default;
+ }));
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
+ .SetClaim(Claims.Subject, "Bob le Bricoleur")
+ .SetClaim(Claims.Prefixes.Private + "_private_claim", "value");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.NotNull(response.IdToken);
+ Assert.NotNull(response.RefreshToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_RefreshTokenIsIssuedForAuthorizationCodeRequestsWhenRollingTokensAreEnabled()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+ options.UseRollingTokens();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
+ Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetPresenters("Fabrikam")
+ .SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ Code = "SplxlOBeZQQYbYS6WxSbIA",
+ GrantType = GrantTypes.AuthorizationCode,
+ RedirectUri = "http://www.fabrikam.com/path"
+ });
+
+ // Assert
+ Assert.NotNull(response.RefreshToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_RefreshTokenIsAlwaysIssuedWhenRollingTokensAreEnabled()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+ options.UseRollingTokens();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.NotNull(response.RefreshToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_RefreshTokenIsNotIssuedWhenRollingTokensAreDisabled()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.Null(response.RefreshToken);
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_AuthorizationCodeIsAutomaticallyRedeemed()
+ {
+ // Arrange
+ var token = new OpenIddictToken();
+
+ var manager = CreateTokenManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()))
+ .ReturnsAsync(token);
+
+ mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
+ .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()))
+ .ReturnsAsync(true);
+
+ mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny()))
+ .ReturnsAsync(true);
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
+ Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetPresenters("Fabrikam")
+ .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.Services.AddSingleton(CreateApplicationManager(mock =>
+ {
+ var application = new OpenIddictApplication();
+
+ mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()))
+ .ReturnsAsync(application);
+
+ mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny()))
+ .ReturnsAsync(ClientTypes.Public);
+ }));
+
+ options.Services.AddSingleton(manager);
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ Code = "SplxlOBeZQQYbYS6WxSbIA",
+ GrantType = GrantTypes.AuthorizationCode,
+ RedirectUri = "http://www.fabrikam.com/path"
+ });
+
+ // Assert
+ Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce());
+ Mock.Get(manager).Verify(manager => manager.TryRedeemAsync(token, It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_ReturnsErrorResponseWhenRedeemingAuthorizationCodeFails()
+ {
+ // Arrange
+ var token = new OpenIddictToken();
+
+ var manager = CreateTokenManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()))
+ .ReturnsAsync(token);
+
+ mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
+ .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()))
+ .ReturnsAsync(true);
+
+ mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny()))
+ .ReturnsAsync(false);
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
+ Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetPresenters("Fabrikam")
+ .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.Services.AddSingleton(CreateApplicationManager(mock =>
+ {
+ var application = new OpenIddictApplication();
+
+ mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()))
+ .ReturnsAsync(application);
+
+ mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny()))
+ .ReturnsAsync(ClientTypes.Public);
+ }));
+
+ options.Services.AddSingleton(manager);
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ Code = "SplxlOBeZQQYbYS6WxSbIA",
+ GrantType = GrantTypes.AuthorizationCode,
+ RedirectUri = "http://www.fabrikam.com/path"
+ });
+
+ // Assert
+ Assert.Equal(Errors.InvalidGrant, response.Error);
+ Assert.Equal("The specified authorization code is no longer valid.", response.ErrorDescription);
+
+ Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce());
+ Mock.Get(manager).Verify(manager => manager.TryRedeemAsync(token, It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_RefreshTokenIsAutomaticallyRedeemedWhenRollingTokensAreEnabled()
+ {
+ // Arrange
+ var token = new OpenIddictToken();
+
+ var manager = CreateTokenManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()))
+ .ReturnsAsync(token);
+
+ mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
+ .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
+ .ReturnsAsync(false);
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()))
+ .ReturnsAsync(true);
+
+ mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny()))
+ .ReturnsAsync(true);
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.UseRollingTokens();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
+ .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.Services.AddSingleton(manager);
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.NotNull(response.RefreshToken);
+
+ Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce());
+ Mock.Get(manager).Verify(manager => manager.TryRedeemAsync(token, It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_ReturnsErrorResponseWhenRedeemingRefreshTokenFails()
+ {
+ // Arrange
+ var token = new OpenIddictToken();
+
+ var manager = CreateTokenManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()))
+ .ReturnsAsync(token);
+
+ mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
+ .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
+ .ReturnsAsync(false);
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()))
+ .ReturnsAsync(true);
+
+ mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny()))
+ .ReturnsAsync(false);
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.UseRollingTokens();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
+ .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.Services.AddSingleton(manager);
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.Equal(Errors.InvalidGrant, response.Error);
+ Assert.Equal("The specified refresh token is no longer valid.", response.ErrorDescription);
+
+ Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce());
+ Mock.Get(manager).Verify(manager => manager.TryRedeemAsync(token, It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_RefreshTokenIsNotRedeemedWhenRollingTokensAreDisabled()
+ {
+ // Arrange
+ var token = new OpenIddictToken();
+
+ var manager = CreateTokenManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()))
+ .ReturnsAsync(token);
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
+ .ReturnsAsync(false);
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()))
+ .ReturnsAsync(true);
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
+ .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.Services.AddSingleton(manager);
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.Null(response.RefreshToken);
+
+ Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce());
+ Mock.Get(manager).Verify(manager => manager.TryRedeemAsync(token, It.IsAny()), Times.Never());
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_PreviousTokensAreAutomaticallyRevokedWhenRollingTokensAreEnabled()
+ {
+ // Arrange
+ var tokens = new[]
+ {
+ new OpenIddictToken(),
+ new OpenIddictToken(),
+ new OpenIddictToken()
+ };
+
+ var manager = CreateTokenManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()))
+ .ReturnsAsync(tokens[0]);
+
+ mock.Setup(manager => manager.GetIdAsync(tokens[0], It.IsAny()))
+ .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
+
+ mock.Setup(manager => manager.GetIdAsync(tokens[1], It.IsAny()))
+ .ReturnsAsync("481FCAC6-06BC-43EE-92DB-37A78AA09B595073CC313103");
+
+ mock.Setup(manager => manager.GetIdAsync(tokens[2], It.IsAny()))
+ .ReturnsAsync("3BEA7A94-5ADA-49AF-9F41-8AB6156E31A8");
+
+ mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny()))
+ .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
+
+ mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny()))
+ .ReturnsAsync(false);
+
+ mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Valid, It.IsAny()))
+ .ReturnsAsync(true);
+
+ mock.Setup(manager => manager.TryRedeemAsync(tokens[0], It.IsAny()))
+ .ReturnsAsync(true);
+
+ mock.Setup(manager => manager.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()))
+ .Returns(tokens.ToAsyncEnumerable());
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.UseRollingTokens();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
+ .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
+ .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.Services.AddSingleton(CreateAuthorizationManager(mock =>
+ {
+ var authorization = new OpenIddictAuthorization();
+
+ mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()))
+ .ReturnsAsync(authorization);
+
+ mock.Setup(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny()))
+ .ReturnsAsync(true);
+ }));
+
+ options.Services.AddSingleton(manager);
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.NotNull(response.RefreshToken);
+
+ Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce());
+ Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[0], It.IsAny()), Times.Never());
+ Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[1], It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[2], It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_PreviousTokensAreNotRevokedWhenRollingTokensAreDisabled()
+ {
+ // Arrange
+ var tokens = new[]
+ {
+ new OpenIddictToken(),
+ new OpenIddictToken(),
+ new OpenIddictToken()
+ };
+
+ var manager = CreateTokenManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()))
+ .ReturnsAsync(tokens[0]);
+
+ mock.Setup(manager => manager.GetIdAsync(tokens[0], It.IsAny()))
+ .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
+
+ mock.Setup(manager => manager.GetIdAsync(tokens[1], It.IsAny()))
+ .ReturnsAsync("481FCAC6-06BC-43EE-92DB-37A78AA09B595073CC313103");
+
+ mock.Setup(manager => manager.GetIdAsync(tokens[2], It.IsAny()))
+ .ReturnsAsync("3BEA7A94-5ADA-49AF-9F41-8AB6156E31A8");
+
+ mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny()))
+ .ReturnsAsync(false);
+
+ mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Valid, It.IsAny()))
+ .ReturnsAsync(true);
+
+ mock.Setup(manager => manager.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()))
+ .Returns(tokens.ToAsyncEnumerable());
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
+ .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0")
+ .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.Services.AddSingleton(CreateAuthorizationManager(mock =>
+ {
+ var authorization = new OpenIddictAuthorization();
+
+ mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()))
+ .ReturnsAsync(authorization);
+
+ mock.Setup(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny()))
+ .ReturnsAsync(true);
+ }));
+
+ options.Services.AddSingleton(manager);
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.NotNull(response.AccessToken);
+ Assert.Null(response.RefreshToken);
+
+ Mock.Get(manager).Verify(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.AtLeastOnce());
+ Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[0], It.IsAny()), Times.Never());
+ Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[1], It.IsAny()), Times.Never());
+ Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(tokens[2], It.IsAny()), Times.Never());
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_ExtendsLifetimeWhenRollingTokensAreDisabledAndSlidingExpirationEnabled()
+ {
+ // Arrange
+ var token = new OpenIddictToken();
+
+ var manager = CreateTokenManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()))
+ .ReturnsAsync(token);
+
+ mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
+ .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
+ .ReturnsAsync(false);
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()))
+ .ReturnsAsync(true);
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
+ .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.Services.AddSingleton(manager);
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.Null(response.RefreshToken);
+
+ Mock.Get(manager).Verify(manager => manager.TryExtendAsync(token,
+ It.IsAny(), It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_DoesNotExtendLifetimeWhenSlidingExpirationIsDisabled()
+ {
+ // Arrange
+ var token = new OpenIddictToken();
+
+ var manager = CreateTokenManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()))
+ .ReturnsAsync(token);
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
+ .ReturnsAsync(false);
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()))
+ .ReturnsAsync(true);
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.DisableSlidingExpiration();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
+ .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.Services.AddSingleton(manager);
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.Null(response.RefreshToken);
+
+ Mock.Get(manager).Verify(manager => manager.TryExtendAsync(token,
+ It.IsAny(), It.IsAny()), Times.Never());
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_DoesNotUpdateExpirationDateWhenAlreadyNull()
+ {
+ // Arrange
+ var token = new OpenIddictToken();
+
+ var manager = CreateTokenManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()))
+ .ReturnsAsync(token);
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
+ .ReturnsAsync(false);
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()))
+ .ReturnsAsync(true);
+
+ mock.Setup(manager => manager.GetExpirationDateAsync(token, It.IsAny()))
+ .ReturnsAsync(value: null);
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
+ .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.Services.AddSingleton(manager);
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.Null(response.RefreshToken);
+
+ Mock.Get(manager).Verify(manager => manager.TryExtendAsync(token, null, It.IsAny()), Times.Never());
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_SetsExpirationDateToNullWhenLifetimeIsNull()
+ {
+ // Arrange
+ var token = new OpenIddictToken();
+
+ var manager = CreateTokenManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()))
+ .ReturnsAsync(token);
+
+ mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
+ .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
+ .ReturnsAsync(false);
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()))
+ .ReturnsAsync(true);
+
+ mock.Setup(manager => manager.GetExpirationDateAsync(token, It.IsAny()))
+ .ReturnsAsync(DateTimeOffset.Now + TimeSpan.FromDays(1));
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.SetRefreshTokenLifetime(lifetime: null);
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
+ .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.Services.AddSingleton(manager);
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.Null(response.RefreshToken);
+
+ Mock.Get(manager).Verify(manager => manager.TryExtendAsync(token, null, It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_IgnoresErrorWhenExtendingLifetimeOfExistingTokenFailed()
+ {
+ // Arrange
+ var token = new OpenIddictToken();
+
+ var manager = CreateTokenManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()))
+ .ReturnsAsync(token);
+
+ mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
+ .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
+ .ReturnsAsync(false);
+
+ mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()))
+ .ReturnsAsync(true);
+
+ mock.Setup(manager => manager.TryExtendAsync(token, It.IsAny(), It.IsAny()))
+ .ReturnsAsync(false);
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("8xLOxBtZp8", context.Token);
+ Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetScopes(Scopes.OpenId, Scopes.OfflineAccess)
+ .SetInternalTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
+ options.Services.AddSingleton(manager);
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ GrantType = GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.NotNull(response.AccessToken);
+
+ Mock.Get(manager).Verify(manager => manager.TryExtendAsync(token,
+ It.IsAny(), It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_AdHocAuthorizationIsAutomaticallyCreated()
+ {
+ // Arrange
+ var token = new OpenIddictToken();
+
+ var manager = CreateAuthorizationManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByIdAsync("1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny()))
+ .ReturnsAsync(new OpenIddictAuthorization());
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.Services.AddSingleton(CreateApplicationManager(mock =>
+ {
+ var application = new OpenIddictApplication();
+
+ mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()))
+ .ReturnsAsync(application);
+
+ mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()))
+ .ReturnsAsync(true);
+
+ mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny()))
+ .ReturnsAsync(ClientTypes.Public);
+
+ mock.Setup(manager => manager.GetIdAsync(application, It.IsAny()))
+ .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
+ }));
+
+ options.Services.AddSingleton(CreateTokenManager(mock =>
+ {
+ mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny()))
+ .ReturnsAsync(token);
+
+ mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
+ .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
+ }));
+
+ options.Services.AddSingleton(manager);
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ RedirectUri = "http://www.fabrikam.com/path",
+ ResponseType = ResponseTypes.Code,
+ });
+
+ // Assert
+ Assert.NotNull(response.Code);
+
+ Mock.Get(manager).Verify(manager => manager.CreateAsync(
+ It.Is(descriptor =>
+ descriptor.ApplicationId == "3E228451-1555-46F7-A471-951EFBA23A56" &&
+ descriptor.Subject == "Bob le Magnifique" &&
+ descriptor.Type == AuthorizationTypes.AdHoc),
+ It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ProcessSignIn_AdHocAuthorizationIsNotCreatedWhenAuthorizationStorageIsDisabled()
+ {
+ // Arrange
+ var token = new OpenIddictToken();
+
+ var manager = CreateAuthorizationManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByIdAsync("1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny()))
+ .ReturnsAsync(new OpenIddictAuthorization());
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.Services.AddSingleton(CreateApplicationManager(mock =>
+ {
+ var application = new OpenIddictApplication();
+
+ mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()))
+ .ReturnsAsync(application);
+
+ mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()))
+ .ReturnsAsync(true);
+
+ mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny()))
+ .ReturnsAsync(ClientTypes.Public);
+
+ mock.Setup(manager => manager.GetIdAsync(application, It.IsAny()))
+ .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
+ }));
+
+ options.Services.AddSingleton(CreateTokenManager(mock =>
+ {
+ mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny()))
+ .ReturnsAsync(token);
+
+ mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
+ .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
+ }));
+
+ options.Services.AddSingleton(manager);
+
+ options.DisableAuthorizationStorage();
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ RedirectUri = "http://www.fabrikam.com/path",
+ ResponseType = ResponseTypes.Code,
+ });
+
+ // Assert
+ Assert.NotNull(response.Code);
+
+ Mock.Get(manager).Verify(manager => manager.CreateAsync(It.IsAny(), It.IsAny()), Times.Never());
+ }
+
protected virtual void ConfigureServices(IServiceCollection services)
{
services.AddOpenIddict()
@@ -494,11 +2905,8 @@ namespace OpenIddict.Server.FunctionalTests
{
builder.UseInlineHandler(context =>
{
- var identity = new ClaimsIdentity("Bearer");
- identity.AddClaim(Claims.Subject, "Bob le Magnifique");
-
- context.Principal = new ClaimsPrincipal(identity);
- context.HandleAuthentication();
+ context.Principal ??= new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetClaim(Claims.Subject, "Bob le Magnifique");
return default;
});
@@ -510,11 +2918,8 @@ namespace OpenIddict.Server.FunctionalTests
{
builder.UseInlineHandler(context =>
{
- var identity = new ClaimsIdentity("Bearer");
- identity.AddClaim(Claims.Subject, "Bob le Magnifique");
-
- context.Principal = new ClaimsPrincipal(identity);
- context.HandleAuthentication();
+ context.Principal ??= new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetClaim(Claims.Subject, "Bob le Magnifique");
return default;
});