diff --git a/src/OpenIddict/OpenIddictProvider.Exchange.cs b/src/OpenIddict/OpenIddictProvider.Exchange.cs index 8cb3992b..ebac7572 100644 --- a/src/OpenIddict/OpenIddictProvider.Exchange.cs +++ b/src/OpenIddict/OpenIddictProvider.Exchange.cs @@ -277,6 +277,7 @@ namespace OpenIddict // response will be returned to the client application. await TryRevokeAuthorizationAsync(context.Ticket, context.HttpContext); await TryRevokeTokensAsync(context.Ticket, context.HttpContext); + await TryRevokeTokenAsync(token, context.HttpContext); logger.LogError("The token request was rejected because the authorization code " + "or refresh token '{Identifier}' has already been redeemed.", identifier); diff --git a/src/OpenIddict/OpenIddictProvider.Helpers.cs b/src/OpenIddict/OpenIddictProvider.Helpers.cs index 051b396f..03b1ca9c 100644 --- a/src/OpenIddict/OpenIddictProvider.Helpers.cs +++ b/src/OpenIddict/OpenIddictProvider.Helpers.cs @@ -417,11 +417,38 @@ namespace OpenIddict } } - private async Task TryRevokeTokensAsync([NotNull] AuthenticationTicket ticket, [NotNull] HttpContext context) + private async Task TryRevokeTokenAsync([NotNull] TToken token, [NotNull] HttpContext context) { var logger = context.RequestServices.GetRequiredService>>(); var tokens = context.RequestServices.GetRequiredService>(); + var identifier = await tokens.GetIdAsync(token); + Debug.Assert(!string.IsNullOrEmpty(identifier), "The token identifier shouldn't be null or empty."); + + try + { + // Note: the request cancellation token is deliberately not used here to ensure the caller + // cannot prevent this operation from being executed by resetting the TCP connection. + await tokens.RevokeAsync(token); + + logger.LogInformation("The token '{Identifier}' was automatically revoked.", identifier); + + return true; + } + + catch (Exception exception) + { + logger.LogWarning(0, exception, "An exception occurred while trying to revoke " + + "the token '{Identifier}'.", identifier); + + return false; + } + } + + private async Task TryRevokeTokensAsync([NotNull] AuthenticationTicket ticket, [NotNull] HttpContext context) + { + var tokens = context.RequestServices.GetRequiredService>(); + // Note: if the authorization identifier is null, return true as no tokens need to be revoked. var identifier = ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId); if (string.IsNullOrEmpty(identifier)) @@ -431,23 +458,13 @@ namespace OpenIddict foreach (var token in await tokens.FindByAuthorizationIdAsync(identifier)) { - try + // Don't change the status of the token used in the token request. + if (string.Equals(ticket.GetTokenId(), await tokens.GetIdAsync(token), StringComparison.Ordinal)) { - // Note: the request cancellation token is deliberately not used here to ensure the caller - // cannot prevent this operation from being executed by resetting the TCP connection. - await tokens.RevokeAsync(token); - - logger.LogInformation("The token '{Identifier}' was automatically revoked.", - await tokens.GetIdAsync(token)); + continue; } - catch (Exception exception) - { - logger.LogWarning(0, exception, "An exception occurred while trying to revoke the tokens " + - "associated with the token '{Identifier}'.", - await tokens.GetIdAsync(token)); - return false; - } + await TryRevokeTokenAsync(token, context); } return true; diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.cs index b373345a..c567c76f 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.cs @@ -825,6 +825,7 @@ namespace OpenIddict.Tests Assert.NotNull(response.RefreshToken); Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[0], It.IsAny()), Times.Never()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[1], It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[2], It.IsAny()), Times.Once()); } @@ -894,6 +895,7 @@ namespace OpenIddict.Tests Assert.Null(response.RefreshToken); Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[0], It.IsAny()), Times.Never()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[1], It.IsAny()), Times.Never()); Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[2], It.IsAny()), Times.Never()); }