diff --git a/src/OpenIddict/OpenIddictExtensions.cs b/src/OpenIddict/OpenIddictExtensions.cs
index e6305361..e68662d4 100644
--- a/src/OpenIddict/OpenIddictExtensions.cs
+++ b/src/OpenIddict/OpenIddictExtensions.cs
@@ -95,6 +95,10 @@ namespace Microsoft.AspNetCore.Builder {
"client credentials, password and refresh token flows.");
}
+ if (options.RevocationEndpointPath.HasValue && options.DisableTokenRevocation) {
+ throw new InvalidOperationException("The revocation endpoint cannot be enabled when token revocation is disabled.");
+ }
+
return app.UseOpenIdConnectServer(options);
}
@@ -479,6 +483,21 @@ namespace Microsoft.AspNetCore.Builder {
return builder.Configure(options => options.UseSlidingExpiration = false);
}
+ ///
+ /// Disables token revocation, so that authorization code and
+ /// refresh tokens are never stored and cannot be revoked.
+ /// Using this option is generally not recommended.
+ ///
+ /// The services builder used by OpenIddict to register new services.
+ /// The .
+ public static OpenIddictBuilder DisableTokenRevocation([NotNull] this OpenIddictBuilder builder) {
+ if (builder == null) {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ return builder.Configure(options => options.DisableTokenRevocation = true);
+ }
+
///
/// Enables the authorization endpoint.
///
diff --git a/src/OpenIddict/OpenIddictOptions.cs b/src/OpenIddict/OpenIddictOptions.cs
index 59a7c218..8a18c3f8 100644
--- a/src/OpenIddict/OpenIddictOptions.cs
+++ b/src/OpenIddict/OpenIddictOptions.cs
@@ -28,6 +28,13 @@ namespace OpenIddict {
///
public IDistributedCache Cache { get; set; }
+ ///
+ /// Gets or sets a boolean indicating whether token revocation should be disabled.
+ /// When disabled, authorization code and refresh tokens are not stored
+ /// and cannot be revoked. Using this option is generally not recommended.
+ ///
+ public bool DisableTokenRevocation { get; set; }
+
///
/// Gets or sets a boolean indicating whether request caching should be enabled.
/// When enabled, both authorization and logout requests are automatically stored
diff --git a/src/OpenIddict/OpenIddictProvider.Exchange.cs b/src/OpenIddict/OpenIddictProvider.Exchange.cs
index 330c9282..f09d11cf 100644
--- a/src/OpenIddict/OpenIddictProvider.Exchange.cs
+++ b/src/OpenIddict/OpenIddictProvider.Exchange.cs
@@ -182,58 +182,55 @@ namespace OpenIddict {
}
public override async Task HandleTokenRequest([NotNull] HandleTokenRequestContext context) {
+ var options = context.HttpContext.RequestServices.GetRequiredService>();
var logger = context.HttpContext.RequestServices.GetRequiredService>>();
var tokens = context.HttpContext.RequestServices.GetRequiredService>();
- if (context.Request.IsAuthorizationCodeGrantType()) {
+ if (!options.Value.DisableTokenRevocation && (context.Request.IsAuthorizationCodeGrantType() ||
+ context.Request.IsRefreshTokenGrantType())) {
Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null.");
- // Extract the token identifier from the authorization code.
+ // Extract the token identifier from the authentication ticket.
var identifier = context.Ticket.GetTicketId();
Debug.Assert(!string.IsNullOrEmpty(identifier),
- "The authorization code should contain a ticket identifier.");
+ "The authentication ticket should contain a ticket identifier.");
- // Retrieve the token from the database and ensure it is still valid.
- var token = await tokens.FindByIdAsync(identifier, context.HttpContext.RequestAborted);
- if (token == null) {
- logger.LogError("The token request was rejected because the authorization code was revoked.");
+ if (context.Request.IsAuthorizationCodeGrantType()) {
+ // Retrieve the token from the database and ensure it is still valid.
+ var token = await tokens.FindByIdAsync(identifier, context.HttpContext.RequestAborted);
+ if (token == null) {
+ logger.LogError("The token request was rejected because the authorization code was revoked.");
- context.Reject(
- error: OpenIdConnectConstants.Errors.InvalidGrant,
- description: "The authorization code is no longer valid.");
+ context.Reject(
+ error: OpenIdConnectConstants.Errors.InvalidGrant,
+ description: "The authorization code is no longer valid.");
- return;
- }
+ return;
+ }
- // Revoke the authorization code to prevent token reuse.
- await tokens.RevokeAsync(token, context.HttpContext.RequestAborted);
- }
-
- else if (context.Request.IsRefreshTokenGrantType()) {
- Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null.");
-
- // Extract the token identifier from the refresh token.
- var identifier = context.Ticket.GetTicketId();
- Debug.Assert(!string.IsNullOrEmpty(identifier),
- "The refresh token should contain a ticket identifier.");
-
- // Retrieve the token from the database and ensure it is still valid.
- var token = await tokens.FindByIdAsync(identifier, context.HttpContext.RequestAborted);
- if (token == null) {
- logger.LogError("The token request was rejected because the refresh token was revoked.");
-
- context.Reject(
- error: OpenIdConnectConstants.Errors.InvalidGrant,
- description: "The refresh token is no longer valid.");
-
- return;
+ // Revoke the authorization code to prevent token reuse.
+ await tokens.RevokeAsync(token, context.HttpContext.RequestAborted);
}
- // When sliding expiration is enabled, immediately
- // revoke the refresh token to prevent future reuse.
- // See https://tools.ietf.org/html/rfc6749#section-6.
- if (context.Options.UseSlidingExpiration) {
- await tokens.RevokeAsync(token, context.HttpContext.RequestAborted);
+ else if (context.Request.IsRefreshTokenGrantType()) {
+ // Retrieve the token from the database and ensure it is still valid.
+ var token = await tokens.FindByIdAsync(identifier, context.HttpContext.RequestAborted);
+ if (token == null) {
+ logger.LogError("The token request was rejected because the refresh token was revoked.");
+
+ context.Reject(
+ error: OpenIdConnectConstants.Errors.InvalidGrant,
+ description: "The refresh token is no longer valid.");
+
+ return;
+ }
+
+ // When sliding expiration is enabled, immediately
+ // revoke the refresh token to prevent future reuse.
+ // See https://tools.ietf.org/html/rfc6749#section-6.
+ if (context.Options.UseSlidingExpiration) {
+ await tokens.RevokeAsync(token, context.HttpContext.RequestAborted);
+ }
}
}
diff --git a/src/OpenIddict/OpenIddictProvider.Introspection.cs b/src/OpenIddict/OpenIddictProvider.Introspection.cs
index 31253c66..ca5c31bb 100644
--- a/src/OpenIddict/OpenIddictProvider.Introspection.cs
+++ b/src/OpenIddict/OpenIddictProvider.Introspection.cs
@@ -13,6 +13,7 @@ using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using OpenIddict.Core;
namespace OpenIddict {
@@ -89,6 +90,7 @@ namespace OpenIddict {
}
public override async Task HandleIntrospectionRequest([NotNull] HandleIntrospectionRequestContext context) {
+ var options = context.HttpContext.RequestServices.GetRequiredService>();
var logger = context.HttpContext.RequestServices.GetRequiredService>>();
var tokens = context.HttpContext.RequestServices.GetRequiredService>();
@@ -110,7 +112,7 @@ namespace OpenIddict {
}
// When the received ticket is revocable, ensure it is still valid.
- if (context.Ticket.IsAuthorizationCode() || context.Ticket.IsRefreshToken()) {
+ if (!options.Value.DisableTokenRevocation && (context.Ticket.IsAuthorizationCode() || context.Ticket.IsRefreshToken())) {
// Retrieve the token from the database using the unique identifier stored in the authentication ticket:
// if the corresponding entry cannot be found, return Active = false to indicate that is is no longer valid.
var token = await tokens.FindByIdAsync(context.Ticket.GetTicketId(), context.HttpContext.RequestAborted);
diff --git a/src/OpenIddict/OpenIddictProvider.Revocation.cs b/src/OpenIddict/OpenIddictProvider.Revocation.cs
index 84eaab89..ba0adf04 100644
--- a/src/OpenIddict/OpenIddictProvider.Revocation.cs
+++ b/src/OpenIddict/OpenIddictProvider.Revocation.cs
@@ -23,6 +23,8 @@ namespace OpenIddict {
var logger = context.HttpContext.RequestServices.GetRequiredService>>();
var options = context.HttpContext.RequestServices.GetRequiredService>();
+ Debug.Assert(!options.Value.DisableTokenRevocation, "Token revocation support shouldn't be disabled at this stage.");
+
// When token_type_hint is specified, reject the request if it doesn't correspond to a revocable token.
if (!string.IsNullOrEmpty(context.Request.TokenTypeHint) &&
!string.Equals(context.Request.TokenTypeHint, OpenIdConnectConstants.TokenTypeHints.AuthorizationCode) &&
diff --git a/src/OpenIddict/OpenIddictProvider.Serialization.cs b/src/OpenIddict/OpenIddictProvider.Serialization.cs
index 0fcb6141..f1964163 100644
--- a/src/OpenIddict/OpenIddictProvider.Serialization.cs
+++ b/src/OpenIddict/OpenIddictProvider.Serialization.cs
@@ -11,37 +11,44 @@ using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
using OpenIddict.Core;
namespace OpenIddict {
public partial class OpenIddictProvider : OpenIdConnectServerProvider
where TApplication : class where TAuthorization : class where TScope : class where TToken : class {
public override async Task SerializeAuthorizationCode([NotNull] SerializeAuthorizationCodeContext context) {
+ var options = context.HttpContext.RequestServices.GetRequiredService>();
var tokens = context.HttpContext.RequestServices.GetRequiredService>();
- var identifier = await tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, context.HttpContext.RequestAborted);
- if (string.IsNullOrEmpty(identifier)) {
- throw new InvalidOperationException("The unique key associated with an authorization code cannot be null or empty.");
- }
+ if (!options.Value.DisableTokenRevocation) {
+ var identifier = await tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, context.HttpContext.RequestAborted);
+ if (string.IsNullOrEmpty(identifier)) {
+ throw new InvalidOperationException("The unique key associated with an authorization code cannot be null or empty.");
+ }
- // Attach the key returned by the underlying store
- // to the authorization code to override the default GUID
- // generated by the OpenID Connect server middleware.
- context.Ticket.SetTicketId(identifier);
+ // Attach the key returned by the underlying store
+ // to the authorization code to override the default GUID
+ // generated by the OpenID Connect server middleware.
+ context.Ticket.SetTicketId(identifier);
+ }
}
public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context) {
+ var options = context.HttpContext.RequestServices.GetRequiredService>();
var tokens = context.HttpContext.RequestServices.GetRequiredService>();
- var identifier = await tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, context.HttpContext.RequestAborted);
- if (string.IsNullOrEmpty(identifier)) {
- throw new InvalidOperationException("The unique key associated with a refresh token cannot be null or empty.");
- }
+ if (!options.Value.DisableTokenRevocation) {
+ var identifier = await tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, context.HttpContext.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.
- context.Ticket.SetTicketId(identifier);
+ // Attach the key returned by the underlying store
+ // to the refresh token to override the default GUID
+ // generated by the OpenID Connect server middleware.
+ context.Ticket.SetTicketId(identifier);
+ }
}
}
}
\ No newline at end of file
diff --git a/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs b/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs
index e5aa536a..48da126b 100644
--- a/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs
+++ b/test/OpenIddict.Tests/OpenIddictExtensionsTests.cs
@@ -15,7 +15,7 @@ using Xunit;
namespace OpenIddict.Tests {
public class OpenIddictExtensionsTests {
[Fact]
- public void UseOpenIddict_AnExceptionIsThrownWhenServicesAreNotRegistered() {
+ public void UseOpenIddict_ThrowsAnExceptionWhenServicesAreNotRegistered() {
// Arrange
var services = new ServiceCollection();
@@ -29,7 +29,7 @@ namespace OpenIddict.Tests {
}
[Fact]
- public void UseOpenIddict_AnExceptionIsThrownWhenNoDistributedCacheIsRegisteredIfRequestCachingIsEnabled() {
+ public void UseOpenIddict_ThrowsAnExceptionWhenNoDistributedCacheIsRegisteredIfRequestCachingIsEnabled() {
// Arrange
var services = new ServiceCollection();
@@ -46,7 +46,7 @@ namespace OpenIddict.Tests {
}
[Fact]
- public void UseOpenIddict_AnExceptionIsThrownWhenNoSigningCredentialsIsRegistered() {
+ public void UseOpenIddict_ThrowsAnExceptionWhenNoSigningCredentialsIsRegistered() {
// Arrange
var services = new ServiceCollection();
services.AddOpenIddict();
@@ -62,7 +62,7 @@ namespace OpenIddict.Tests {
}
[Fact]
- public void UseOpenIddict_AnExceptionIsThrownWhenNoFlowIsEnabled() {
+ public void UseOpenIddict_ThrowsAnExceptionWhenNoFlowIsEnabled() {
// Arrange
var services = new ServiceCollection();
@@ -83,7 +83,7 @@ namespace OpenIddict.Tests {
[Theory]
[InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode)]
[InlineData(OpenIdConnectConstants.GrantTypes.Implicit)]
- public void UseOpenIddict_AnExceptionIsThrownWhenAuthorizationEndpointIsDisabled(string flow) {
+ public void UseOpenIddict_ThrowsAnExceptionWhenAuthorizationEndpointIsDisabled(string flow) {
// Arrange
var services = new ServiceCollection();
@@ -109,7 +109,7 @@ namespace OpenIddict.Tests {
[InlineData(OpenIdConnectConstants.GrantTypes.ClientCredentials)]
[InlineData(OpenIdConnectConstants.GrantTypes.Password)]
[InlineData(OpenIdConnectConstants.GrantTypes.RefreshToken)]
- public void UseOpenIddict_AnExceptionIsThrownWhenTokenEndpointIsDisabled(string flow) {
+ public void UseOpenIddict_ThrowsAnExceptionWhenTokenEndpointIsDisabled(string flow) {
// Arrange
var services = new ServiceCollection();
@@ -131,6 +131,29 @@ namespace OpenIddict.Tests {
"client credentials, password and refresh token flows.", exception.Message);
}
+ [Fact]
+ public void UseOpenIddict_ThrowsAnExceptionWhenTokenRevocationIsDisabled() {
+ // Arrange
+ var services = new ServiceCollection();
+
+ services.AddOpenIddict()
+ .AddSigningCertificate(
+ assembly: typeof(OpenIddictProviderTests).GetTypeInfo().Assembly,
+ resource: "OpenIddict.Tests.Certificate.pfx",
+ password: "OpenIddict")
+ .EnableAuthorizationEndpoint("/connect/authorize")
+ .EnableRevocationEndpoint("/connect/revocation")
+ .AllowImplicitFlow()
+ .DisableTokenRevocation();
+
+ var builder = new ApplicationBuilder(services.BuildServiceProvider());
+
+ // Act and assert
+ var exception = Assert.Throws(() => builder.UseOpenIddict());
+
+ Assert.Equal("The revocation endpoint cannot be enabled when token revocation is disabled.", exception.Message);
+ }
+
[Fact]
public void Configure_OptionsAreCorrectlyAmended() {
// Arrange
@@ -413,6 +436,42 @@ namespace OpenIddict.Tests {
Assert.Equal(PathString.Empty, options.Value.CryptographyEndpointPath);
}
+ [Fact]
+ public void DisableSlidingExpiration_SlidingExpirationIsDisabled() {
+ // Arrange
+ var services = new ServiceCollection();
+ services.AddOptions();
+
+ var builder = new OpenIddictBuilder(services);
+
+ // Act
+ builder.DisableSlidingExpiration();
+
+ var provider = services.BuildServiceProvider();
+ var options = provider.GetRequiredService>();
+
+ // Assert
+ Assert.False(options.Value.UseSlidingExpiration);
+ }
+
+ [Fact]
+ public void DisableTokenRevocation_TokenRevocationIsDisabled() {
+ // Arrange
+ var services = new ServiceCollection();
+ services.AddOptions();
+
+ var builder = new OpenIddictBuilder(services);
+
+ // Act
+ builder.DisableTokenRevocation();
+
+ var provider = services.BuildServiceProvider();
+ var options = provider.GetRequiredService>();
+
+ // Assert
+ Assert.True(options.Value.DisableTokenRevocation);
+ }
+
[Fact]
public void EnableAuthorizationEndpoint_AuthorizationEndpointIsEnabled() {
// Arrange
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs
index 975e9401..f2165f44 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs
@@ -7,6 +7,7 @@ using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Moq;
@@ -299,6 +300,98 @@ namespace OpenIddict.Tests {
Mock.Get(manager).Verify(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), Times.Once());
}
+ [Fact]
+ public async Task HandleTokenRequest_AuthorizationCodeRevocationIsIgnoredWhenTokenRevocationIsDisabled() {
+ // Arrange
+ var ticket = new AuthenticationTicket(
+ new ClaimsPrincipal(),
+ new AuthenticationProperties(),
+ OpenIdConnectServerDefaults.AuthenticationScheme);
+
+ ticket.SetPresenters("Fabrikam");
+ ticket.SetTicketId("3E228451-1555-46F7-A471-951EFBA23A56");
+ ticket.SetUsage(OpenIdConnectConstants.Usages.AuthorizationCode);
+
+ var format = new Mock>();
+
+ format.Setup(mock => mock.Unprotect("SplxlOBeZQQYbYS6WxSbIA"))
+ .Returns(ticket);
+
+ 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.Configure(options => options.AuthorizationCodeFormat = format.Object);
+ builder.Configure(options => options.RevocationEndpointPath = PathString.Empty);
+
+ builder.DisableTokenRevocation();
+ });
+
+ var client = new OpenIdConnectClient(server.CreateClient());
+
+ // Act
+ var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
+ ClientId = "Fabrikam",
+ Code = "SplxlOBeZQQYbYS6WxSbIA",
+ GrantType = OpenIdConnectConstants.GrantTypes.AuthorizationCode
+ });
+
+ // Assert
+ Assert.NotNull(response.AccessToken);
+ }
+
+ [Fact]
+ public async Task HandleTokenRequest_RefreshTokenRevocationIsIgnoredWhenTokenRevocationIsDisabled() {
+ // Arrange
+ var ticket = new AuthenticationTicket(
+ new ClaimsPrincipal(),
+ new AuthenticationProperties(),
+ OpenIdConnectServerDefaults.AuthenticationScheme);
+
+ ticket.SetTicketId("60FFF7EA-F98E-437B-937E-5073CC313103");
+ ticket.SetUsage(OpenIdConnectConstants.Usages.RefreshToken);
+
+ var format = new Mock>();
+
+ format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
+ .Returns(ticket);
+
+ 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.Configure(options => options.RefreshTokenFormat = format.Object);
+ builder.Configure(options => options.RevocationEndpointPath = PathString.Empty);
+
+ builder.DisableTokenRevocation();
+ });
+
+ var client = new OpenIdConnectClient(server.CreateClient());
+
+ // Act
+ var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
+ GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken,
+ RefreshToken = "8xLOxBtZp8"
+ });
+
+ // Assert
+ Assert.NotNull(response.AccessToken);
+ }
+
[Fact]
public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationCodeIsExpired() {
// Arrange
diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs
index efd41581..b3bb02df 100644
--- a/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs
+++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs
@@ -8,6 +8,7 @@ using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Moq;
@@ -206,6 +207,110 @@ namespace OpenIddict.Tests {
Assert.False((bool) response[OpenIdConnectConstants.Claims.Active]);
}
+ [Fact]
+ public async Task HandleIntrospectionRequest_AuthorizationCodeRevocationIsIgnoredWhenTokenRevocationIsDisabled() {
+ // Arrange
+ var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
+ identity.AddClaim(ClaimTypes.NameIdentifier, "Bob le Bricoleur");
+
+ var ticket = new AuthenticationTicket(
+ new ClaimsPrincipal(identity),
+ new AuthenticationProperties(),
+ OpenIdConnectServerDefaults.AuthenticationScheme);
+
+ ticket.SetTicketId("3E228451-1555-46F7-A471-951EFBA23A56");
+ ticket.SetUsage(OpenIdConnectConstants.Usages.AuthorizationCode);
+
+ var format = new Mock>();
+
+ format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA"))
+ .Returns(ticket);
+
+ 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.Confidential);
+
+ instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()))
+ .ReturnsAsync(true);
+ }));
+
+ builder.Configure(options => options.AuthorizationCodeFormat = format.Object);
+ builder.Configure(options => options.RevocationEndpointPath = PathString.Empty);
+
+ builder.DisableTokenRevocation();
+ });
+
+ var client = new OpenIdConnectClient(server.CreateClient());
+
+ // Act
+ var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest {
+ ClientId = "Fabrikam",
+ ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
+ Token = "2YotnFZFEjr1zCsicMWpAA"
+ });
+
+ // Assert
+ Assert.True((bool) response[OpenIdConnectConstants.Claims.Active]);
+ }
+
+ [Fact]
+ public async Task HandleIntrospectionRequest_RefreshTokenRevocationIsIgnoredWhenTokenRevocationIsDisabled() {
+ // Arrange
+ var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
+ identity.AddClaim(ClaimTypes.NameIdentifier, "Bob le Bricoleur");
+
+ var ticket = new AuthenticationTicket(
+ new ClaimsPrincipal(identity),
+ new AuthenticationProperties(),
+ OpenIdConnectServerDefaults.AuthenticationScheme);
+
+ ticket.SetTicketId("3E228451-1555-46F7-A471-951EFBA23A56");
+ ticket.SetUsage(OpenIdConnectConstants.Usages.AuthorizationCode);
+
+ var format = new Mock>();
+
+ format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA"))
+ .Returns(ticket);
+
+ 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.Confidential);
+
+ instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()))
+ .ReturnsAsync(true);
+ }));
+
+ builder.Configure(options => options.AuthorizationCodeFormat = format.Object);
+ builder.Configure(options => options.RevocationEndpointPath = PathString.Empty);
+
+ builder.DisableTokenRevocation();
+ });
+
+ var client = new OpenIdConnectClient(server.CreateClient());
+
+ // Act
+ var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest {
+ ClientId = "Fabrikam",
+ ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
+ Token = "2YotnFZFEjr1zCsicMWpAA"
+ });
+
+ // Assert
+ Assert.True((bool) response[OpenIdConnectConstants.Claims.Active]);
+ }
+
[Fact]
public async Task HandleIntrospectionRequest_RequestIsRejectedWhenAuthorizationCodeIsRevoked() {
// Arrange