openiddict / openiddict-samples

.NET samples for OpenIddict
https://documentation.openiddict.com/
Apache License 2.0
725 stars 301 forks source link

How to allow multiple clients to share the same logout? #342

Open feededit opened 2 weeks ago

feededit commented 2 weeks ago

Confirm you've already contributed to this project or that you sponsor it

Version

4.0

Question

VelusiaServer.com VelusiaClientA.com VelusiaClientB.com is logged in. When VelusiaClientA.com logs out, VelusiaServer.com and VelusiaClientA.com log out, but VelusiaClientB.com remains logged in.

How to allow multiple clients to share the same logout?

kevinchalet commented 2 weeks ago

Hi,

How to allow multiple clients to share the same logout?

This scenario requires single sign-out, which is not currently supported by OpenIddict. Backchannel logout may be supported in a future version (see https://github.com/openiddict/openiddict-core/issues/2175), but it will only work with server-side web applications.

As an alternative, you can revoke access tokens issued to specific clients from your end session endpoint (note: OpenIddict 6.0 will introduce new APIs to revoke authorizations and tokens attached to a specific client or subject more efficiently) and use the introspection endpoint to query the current status of a token from time to time, which allows detecting whether a token was revoked or not.

feededit commented 1 week ago

As you know, your answer leads to two questions. If revoke-access-tokens refers to log out and is a function of version 4.0 or lower, please guide me on how to revoke access tokens. I will try to upgrade my project by downloading from 4.0 to 6.0 at ruget and then modifying project code. Is this possible?

kevinchalet commented 1 week ago

Here's how you can revoke all the valid access tokens attached to a specific user directly from the logout endpoint (these APIs are already in OpenIddict 4 and 5):

[HttpGet("~/connect/logout")]
public IActionResult Logout() => View();

[ActionName(nameof(Logout)), HttpPost("~/connect/logout"), ValidateAntiForgeryToken]
public async Task<IActionResult> LogoutPost()
{
    var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
    if (result is not { Succeeded: true })
    {
        return SignOut(
            authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
            properties: new AuthenticationProperties
            {
                RedirectUri = "/"
            });
    }

    var subject = result.Principal.GetClaim(Claims.Subject) ?? throw new InvalidOperationException();

    await foreach (var token in _tokenManager.FindBySubjectAsync(subject))
    {
        if (await _tokenManager.GetTypeAsync(token) is TokenTypeHints.AccessToken)
        {
            await _tokenManager.TryRevokeAsync(token);
        }
    }

    // Ask ASP.NET Core Identity to delete the local and external cookies created
    // when the user agent is redirected from the external identity provider
    // after a successful authentication flow (e.g Google or Facebook).
    await _signInManager.SignOutAsync();

    // Returning a SignOutResult will ask OpenIddict to redirect the user agent
    // to the post_logout_redirect_uri specified by the client application or to
    // the RedirectUri specified in the authentication properties if none was set.
    return SignOut(
        authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
        properties: new AuthenticationProperties
        {
            RedirectUri = "/"
        });

}

Alternatively, you can also just revoke the authorizations, which will automatically cause the associated tokens to be rejected when trying to introspect them:

var subject = result.Principal.GetClaim(Claims.Subject) ?? throw new InvalidOperationException();

// More efficient way of doing it, but OpenIddict 6.0+-only:
// await _authorizationManager.RevokeBySubjectAsync(subject);

// Usable in previous versions of OpenIddict:
await foreach (var authorization in _authorizationManager.FindBySubjectAsync(subject))
{
    await _authorizationManager.TryRevokeAsync(authorization);
}

// Ask ASP.NET Core Identity to delete the local and external cookies created
// when the user agent is redirected from the external identity provider
// after a successful authentication flow (e.g Google or Facebook).
await _signInManager.SignOutAsync();

// Returning a SignOutResult will ask OpenIddict to redirect the user agent
// to the post_logout_redirect_uri specified by the client application or to
// the RedirectUri specified in the authentication properties if none was set.
return SignOut(
    authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
    properties: new AuthenticationProperties
    {
        RedirectUri = "/"
    });

Once it's in place, enable the introspection endpoint in your server configuration and grant your client the introspection endpoint permission. You'll then be able to use OpenIddictClientService.IntrospectTokenAsync() (or any other client supporting the OAuth 2.0 introspection endpoint) to determine if the token is still valid or not at regular intervals:

// If that call fails and throws an exception, the access token is no longer valid.
_ = await _service.IntrospectTokenAsync(new()
{
    Token = "your access token",
    TokenTypeHint = TokenTypeHints.AccessToken
});
feededit commented 1 week ago

The above source works normally in 5.0, but an error of unknown cause occurred in my project.

After performing several tests, I confirmed that my project version was 3.1.1 for the server and 4.0 for the client.

I would like to solve this problem by downloading the server's openiddick library as 5.X from ruget and using the server project code as is.

This approach would likely make my project seriously complex.

Server openiddick library: 5.X (or 4.X) Server code: Code used in 3.1.1 (or modify only the necessary parts) Client openiddick library and code: 4.0

Is it possible?

kevinchalet commented 1 week ago

Using different versions of the OpenIddict packages in the same application is not supported but if you're using the OpenIddict server and client in different projects, it shouldn't be a problem.

That said, both OpenIddict 3.x and 4.x are no longer supported so I strongly encourage you to update all your projects to 5.x:

feededit commented 1 week ago

thank you

I would like to upgrade to 5.x, but I will upgrade to 4.x. For 6.x, I hope there will be an upgrade guide for 4.x

feededit commented 1 week ago

Migration from 4.0 to 5.0

I will download and use the 5.0 sample source. Will my existing 4.X databases be automatically upgraded? If doesn't upgrade automatically, can I do it manually in SSMS?

Added properties
Table   Column name Type    Nullable
OpenIddictApplications  ApplicationType string  Yes
OpenIddictApplications  JsonWebKeySet   string  Yes
OpenIddictApplications  Settings    string  Yes
Renamed properties
Table   Old column name New column name
OpenIddictApplications  Type    ClientType
feededit commented 1 week ago

Velusia in openiddict-samples-dev 5.X version appears following error. When a client logs in, the logged in client is logg off. When VelusiaA.com log in, VelusiaB.com log off, and when VelusiaB.com log in, VelusiaA.com log off. test is result from two clients, so I don't know if all clients are log off.

kevinchalet commented 1 week ago

Will my existing 4.X databases be automatically upgraded?

Nope. If you use EF Core and its built-in migrations feature, add a new migration after updating the packages and execute it: it will update your database.

Velusia in openiddict-samples-dev 5.X version appears following error.

Hum, what error exactly?

feededit commented 1 week ago

I created two Volusia clients. And when I log in one of them, the other client logs off.

feededit commented 1 week ago

problem appears to be occurring in this part of the code.

        [Authorize, FormValueRequired("submit.Accept")]
        [HttpPost("~/connect/authorize"), ValidateAntiForgeryToken]
        public async Task<IActionResult> Accept()
        {
            var request = HttpContext.GetOpenIddictServerRequest() ??
                throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");

            var user = await _userManager.GetUserAsync(User) ??
                throw new InvalidOperationException("The user details cannot be retrieved.");

            var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ??
                throw new InvalidOperationException("Details concerning the calling client application cannot be found.");

            var authorizations = await _authorizationManager.FindAsync(
                subject: await _userManager.GetUserIdAsync(user),
                client: await _applicationManager.GetIdAsync(application),
                status: Statuses.Valid,
                type: AuthorizationTypes.Permanent,
                scopes: request.GetScopes()).ToListAsync();

            if (authorizations.Count is 0 && await _applicationManager.HasConsentTypeAsync(application, ConsentTypes.External))
            {
                return Forbid(
                    authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                    properties: new AuthenticationProperties(new Dictionary<string, string>
                    {
                        [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired,
                        [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                            "The logged in user is not allowed to access this client application."
                    }));
            }

            var identity = new ClaimsIdentity(
                authenticationType: TokenValidationParameters.DefaultAuthenticationType,
                nameType: Claims.Name,
                roleType: Claims.Role);

            identity.SetClaim(Claims.Subject, await _userManager.GetUserIdAsync(user))
                    .SetClaim(Claims.Email, await _userManager.GetEmailAsync(user))
                    .SetClaim(Claims.Name, await _userManager.GetUserNameAsync(user))
                    .SetClaim(Claims.PreferredUsername, await _userManager.GetUserNameAsync(user))
                    .SetClaims(Claims.Role, [.. (await _userManager.GetRolesAsync(user))]);

            identity.SetScopes(request.GetScopes());
            identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());

            var authorization = authorizations.LastOrDefault();
            authorization ??= await _authorizationManager.CreateAsync(
                identity: identity,
                subject: await _userManager.GetUserIdAsync(user),
                client: await _applicationManager.GetIdAsync(application),
                type: AuthorizationTypes.Permanent,
                scopes: identity.GetScopes());

            identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));
            identity.SetDestinations(GetDestinations);

            return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
        }
kevinchalet commented 1 week ago

I created two Volusia clients. And when I log in one of them, the other client logs off.

Weird. Are your two clients hosted on the same domain (e.g localhost)? If so, do you use different cookie names?

feededit commented 1 week ago

I am in testing. So it's localhost.

kevinchalet commented 1 week ago

It's possible the ASP.NET Core authentication cookies are colliding. Try setting a different name in one of your clients via services.ConfigureApplicationCookie(...) if you're using ASP.NET Core Identity or in your services.AddAuthentication().AddCookie(...) call if you're not using Identity.

feededit commented 1 week ago

thank you. I'll let you know the results after testing it.

kevinchalet commented 1 week ago

My pleasure! ❤️

feededit commented 1 week ago

It worked out well. thank you.

However, single sign-out does not work.

VelusiaServer.com VelusiaClientA.com VelusiaClientB.com is logged in. When VelusiaClientA.com logs out, VelusiaServer.com and VelusiaClientA.com log out, but VelusiaClientB.com remains logged in.

        [ActionName(nameof(Logout)), HttpPost("~/connect/logout"), ValidateAntiForgeryToken]
        public async Task<IActionResult> LogoutPost()
        {

            var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

            if (result is not { Succeeded: true })
            {
                return SignOut(
                    authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                    properties: new AuthenticationProperties
                    {
                        RedirectUri = "/"
                    });
            }

            var subject = result.Principal.GetClaim(Claims.Subject) ?? throw new InvalidOperationException();

            await foreach (var token in _tokenManager.FindBySubjectAsync(subject))
            {
                if (await _tokenManager.GetTypeAsync(token) is TokenTypeHints.AccessToken)
                {
                    await _tokenManager.TryRevokeAsync(token);
                }
            }

            await _signInManager.SignOutAsync();

            return SignOut(
                authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                properties: new AuthenticationProperties
                {
                    RedirectUri = "/"
                });
        }
kevinchalet commented 1 week ago

Did you implement this part in your client app? Remember it's not real "single sign-out", you need to poll the token status at regular intervals using introspection:

Once it's in place, enable the introspection endpoint in your server configuration and grant your client the introspection endpoint permission. You'll then be able to use OpenIddictClientService.IntrospectTokenAsync() (or any other client supporting the OAuth 2.0 introspection endpoint) to determine if the token is still valid or not at regular intervals:

// If that call fails and throws an exception, the access token is no longer valid.
_ = await _service.IntrospectTokenAsync(new()
{
    Token = "your access token",
    TokenTypeHint = TokenTypeHints.AccessToken
});
feededit commented 1 week ago

I'm not trying to complete single sign-out. I am satisfied as long as when one client logs out, the other clients also log out. (Only two clients.)

I'm very interested in log out other clients with the cookie name I just resolved.

Couldn't this issue be resolved by log out other clients using the cookie name?

kevinchalet commented 1 week ago

I'm not trying to complete single sign-out. I am satisfied as long as when one client logs out, the other clients also log out. (Only two clients.)

Well, one client or 50, it doesn't change anything, it's still single sign-out, which requires a very specific implementation 😄

Couldn't this issue be resolved by log out other clients using the cookie name?

Well, it offers a limited level of security since a user can copy the cookies and replay them (they are not "invalidated"). I wouldn't really recommend it.

feededit commented 1 week ago

Sorry but I still don't understand.

introspection endpoint : server introspection endpoint permission : client OpenIddictClientService.IntrospectTokenAsync() : ? IntrospectTokenAsync : ?

What do I need to do to know introspection?

kevinchalet commented 1 week ago

OpenIddictClientService.IntrospectTokenAsync() must be used in your client applications to query the current status of a token and invalidate their authentication cookie.

One simple option is to do that in the CookieAuthenticationEvents.OnValidatePrincipal event: if OpenIddictClientService.IntrospectTokenAsync() throws an exception (indicating it's no longer valid), call context.RejectPrincipal() to reject the user principal.

feededit commented 1 week ago

enable the introspection endpoint in your server configuration and grant your client the introspection endpoint permission.

Please guide me on how to configure the server and client startup. (Worker too, if necessary)

server

      services.AddOpenIddict()

            .AddCore(options =>
            {
                options.UseEntityFrameworkCore()
                       .UseDbContext<ApplicationDbContext>();

                options.UseQuartz();
            })

            .AddClient(options =>
            {
                options.AllowAuthorizationCodeFlow();

                options.AddDevelopmentEncryptionCertificate()
                       .AddDevelopmentSigningCertificate();

                options.UseAspNetCore()
                       .EnableStatusCodePagesIntegration()
                       .EnableRedirectionEndpointPassthrough();

                options.UseSystemNetHttp()
                       .SetProductInformation(typeof(Startup).Assembly);

                options.UseWebProviders()
                       .AddGitHub(options =>
                       {
                           options.SetClientId("c4ade52327b01ddacff3")
                                  .SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122")
                                  .SetRedirectUri("callback/login/github");
                       });
            })

            .AddServer(options =>
            {
                options.SetAuthorizationEndpointUris("connect/authorize")
                       .SetLogoutEndpointUris("connect/logout")
                       .SetTokenEndpointUris("connect/token")
                       .SetUserinfoEndpointUris("connect/userinfo");

                options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles);

                options.AllowAuthorizationCodeFlow();

                options.AddDevelopmentEncryptionCertificate()
                       .AddDevelopmentSigningCertificate();

                options.UseAspNetCore()
                       .EnableAuthorizationEndpointPassthrough()
                       .EnableLogoutEndpointPassthrough()
                       .EnableTokenEndpointPassthrough()
                       .EnableUserinfoEndpointPassthrough()
                       .EnableStatusCodePagesIntegration();
            })

            .AddValidation(options =>
            {
                options.UseLocalServer();

                options.UseAspNetCore();
            });

client

        services.AddOpenIddict()
            .AddCore(options =>
            {
                options.UseEntityFrameworkCore()
                       .UseDbContext<ApplicationDbContext>();

                options.UseQuartz();
            })

            .AddClient(options =>
            {
                options.AllowAuthorizationCodeFlow();

                options.AddDevelopmentEncryptionCertificate()
                       .AddDevelopmentSigningCertificate();

                options.UseAspNetCore()
                       .EnableStatusCodePagesIntegration()
                       .EnableRedirectionEndpointPassthrough()
                       .EnablePostLogoutRedirectionEndpointPassthrough();

                options.UseSystemNetHttp()
                       .SetProductInformation(typeof(Startup).Assembly);

               options.AddRegistration(new OpenIddictClientRegistration
                {
                    Issuer = new Uri("https://localhost:44313/", UriKind.Absolute),

                    ClientId = "mvc",
                    ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654",
                    Scopes = { Scopes.Email, Scopes.Profile },

                    RedirectUri = new Uri("callback/login/local", UriKind.Relative),
                    PostLogoutRedirectUri = new Uri("callback/logout/local", UriKind.Relative)
                });
            });
feededit commented 1 week ago

Below code unable access server.

    public class TestController : Controller
    {
        private readonly OpenIddictClientService _service;

        public TestController(OpenIddictClientService service)
        {
            _service = service;
        }

        public async Task<IActionResult> Index()
        {
            string user = "";

            if (User?.Identity is { IsAuthenticated: true })
            {
                user = User.Identity.Name;
                var accessToken = await HttpContext.GetTokenAsync("access_token");

                try
                {
                    _ = await _service.IntrospectTokenAsync(new()
                    {
                        Token = accessToken,
                        TokenTypeHint = TokenTypeHints.AccessToken
                    });
                }
                catch (Exception)
                {
                    user = "log out";
                }
            }

            ViewBag.User = user;

            return View();
        }

    }
kevinchalet commented 1 week ago

You need to add options.SetIntrospectionEndpointUris("connect/introspect") in the server options and update your client applications to give them the OpenIddictConstants.Permissions.Endpoints.Introspection permission.

If it still doesn't work, share your logs and I'll take a look.

feededit commented 1 week ago

"connect/introspect"

You must provide code that handles the server's Introspect endpoint.

feededit commented 1 week ago

The server options appear to be incorrect. The result of the code below is not received.

        [HttpGet("~/connect/introspect")]
        [HttpPost("~/connect/introspect")]
        public IActionResult Introspect()
        {
            return Ok(new { active = true });
        }
                        .AddServer(options =>
                        {
                            options.SetAuthorizationEndpointUris("connect/authorize")
                                   .SetLogoutEndpointUris("connect/logout")
                                   .SetTokenEndpointUris("connect/token")
                                   .SetUserinfoEndpointUris("connect/userinfo")
                                   .SetIntrospectionEndpointUris("connect/introspect");

                            options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles);

                            options.AllowAuthorizationCodeFlow();

                            options.AddDevelopmentEncryptionCertificate()
                                   .AddDevelopmentSigningCertificate();
                            //options.AddSigningCertificate("7daec0b6f69b974fe45373057b90be89fe06e4f2");
                            //options.AddEncryptionCertificate("ad34f6ddfba1acb1c7b39cf34c301ba6c1fad9b5");

                            options.UseAspNetCore()
                                   .EnableAuthorizationEndpointPassthrough()
                                   .EnableLogoutEndpointPassthrough()
                                   .EnableTokenEndpointPassthrough()
                                   .EnableUserinfoEndpointPassthrough()
                                   .EnableStatusCodePagesIntegration();
                        })
kevinchalet commented 1 week ago

You must provide code that handles the server's Introspect endpoint.

Introspection requests are handled for you by OpenIddict itself: you don't need to provide an action for it.

feededit commented 1 week ago

I tested it with just the option settings, but it didn't work. How do I test whether option settings are correct? If I add the code below, will OpenIddict itself be ignored?

        [HttpGet("~/connect/introspect")]
        [HttpPost("~/connect/introspect")]
        public IActionResult Introspect()
        {
            return Ok(new { active = true });
        }
kevinchalet commented 1 week ago

If I add the code below, will OpenIddict itself be ignored?

No.

How do I test whether option settings are correct?

Collect the logs and share them, please.

feededit commented 1 week ago

client

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (10ms) [Parameters=[@p0='?' (Size = 450), @p1='?' (Size = 450), @p2='?' (Size = 450), @p3='?' (Size = 50), @p4='?' (DbType = DateTime2), @p5='?' (DbType = DateTime2), @p6='?' (Size = 4000), @p7='?' (Size = 4000), @p8='?' (DbType = DateTime2), @p9='?' (Size = 100), @p10='?' (Size = 50), @p11='?' (Size = 400), @p12='?' (Size = 50)], CommandType='Text', CommandTimeout='30']
      SET IMPLICIT_TRANSACTIONS OFF;
      SET NOCOUNT ON;
      INSERT INTO [OpenIddictTokens] ([Id], [ApplicationId], [AuthorizationId], [ConcurrencyToken], [CreationDate], [ExpirationDate], [Payload], [Properties], [RedemptionDate], [ReferenceId], [Status], [Subject], [Type])
      VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[@p12='?' (Size = 450), @p0='?' (Size = 450), @p1='?' (Size = 450), @p2='?' (Size = 50), @p13='?' (Size = 50), @p3='?' (DbType = DateTime2), @p4='?' (DbType = DateTime2), @p5='?' (Size = 4000), @p6='?' (Size = 4000), @p7='?' (DbType = DateTime2), @p8='?' (Size = 100), @p9='?' (Size = 50), @p10='?' (Size = 400), @p11='?' (Size = 50)], CommandType='Text', CommandTimeout='30']
      SET IMPLICIT_TRANSACTIONS OFF;
      SET NOCOUNT ON;
      UPDATE [OpenIddictTokens] SET [ApplicationId] = @p0, [AuthorizationId] = @p1, [ConcurrencyToken] = @p2, [CreationDate] = @p3, [ExpirationDate] = @p4, [Payload] = @p5, [Properties] = @p6, [RedemptionDate] = @p7, [ReferenceId] = @p8, [Status] = @p9, [Subject] = @p10, [Type] = @p11
      OUTPUT 1
      WHERE [Id] = @p12 AND [ConcurrencyToken] = @p13;
info: OpenIddict.Client.AspNetCore.OpenIddictClientAspNetCoreHandler[12]
      AuthenticationScheme: OpenIddict.Client.AspNetCore was challenged.
info: OpenIddict.Client.OpenIddictClientDispatcher[0]
      The redirection request was successfully extracted: {
        "code": "[redacted]",
        "state": "E4znDCe5DzMuYig7csE0MjMHV_Vj3qUCy72SA4cDbMc",
        "iss": "https://localhost:7179/"
      }.
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[@__identifier_0='?' (Size = 100)], CommandType='Text', CommandTimeout='30']
      SELECT TOP(1) [o].[Id], [o].[ApplicationId], [o].[AuthorizationId], [o].[ConcurrencyToken], [o].[CreationDate], [o].[ExpirationDate], [o].[Payload], [o].[Properties], [o].[RedemptionDate], [o].[ReferenceId], [o].[Status], [o].[Subject], [o].[Type], [o0].[Id], [o0].[ApplicationType], [o0].[ClientId], [o0].[ClientSecret], [o0].[ClientType], [o0].[ConcurrencyToken], [o0].[ConsentType], [o0].[DisplayName], [o0].[DisplayNames], [o0].[JsonWebKeySet], [o0].[Permissions], [o0].[PostLogoutRedirectUris], [o0].[Properties], [o0].[RedirectUris], [o0].[Requirements], [o0].[Settings], [o1].[Id], [o1].[ApplicationId], [o1].[ConcurrencyToken], [o1].[CreationDate], [o1].[Properties], [o1].[Scopes], [o1].[Status], [o1].[Subject], [o1].[Type]
      FROM [OpenIddictTokens] AS [o]
      LEFT JOIN [OpenIddictApplications] AS [o0] ON [o].[ApplicationId] = [o0].[Id]
      LEFT JOIN [OpenIddictAuthorizations] AS [o1] ON [o].[AuthorizationId] = [o1].[Id]
      WHERE [o].[ReferenceId] = @__identifier_0
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[@p12='?' (Size = 450), @p0='?' (Size = 450), @p1='?' (Size = 450), @p2='?' (Size = 50), @p13='?' (Size = 50), @p3='?' (DbType = DateTime2), @p4='?' (DbType = DateTime2), @p5='?' (Size = 4000), @p6='?' (Size = 4000), @p7='?' (DbType = DateTime2), @p8='?' (Size = 100), @p9='?' (Size = 50), @p10='?' (Size = 400), @p11='?' (Size = 50)], CommandType='Text', CommandTimeout='30']
      SET IMPLICIT_TRANSACTIONS OFF;
      SET NOCOUNT ON;
      UPDATE [OpenIddictTokens] SET [ApplicationId] = @p0, [AuthorizationId] = @p1, [ConcurrencyToken] = @p2, [CreationDate] = @p3, [ExpirationDate] = @p4, [Payload] = @p5, [Properties] = @p6, [RedemptionDate] = @p7, [ReferenceId] = @p8, [Status] = @p9, [Subject] = @p10, [Type] = @p11
      OUTPUT 1
      WHERE [Id] = @p12 AND [ConcurrencyToken] = @p13;
info: OpenIddict.Core.OpenIddictTokenManager[0]
      The token '223cf51b-865b-4e22-a310-dde56f2a25f1' was successfully marked as redeemed.
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.LogicalHandler[100]
      Start processing HTTP request POST https://localhost:7179/connect/token
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.ClientHandler[100]
      Sending HTTP request POST https://localhost:7179/connect/token
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.ClientHandler[101]
      Received HTTP response headers after 377.414ms - 200
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.LogicalHandler[101]
      End processing HTTP request after 377.5797ms - 200
info: OpenIddict.Client.OpenIddictClientDispatcher[0]
      The token request was successfully sent to https://localhost:7179/connect/token: {
        "grant_type": "authorization_code",
        "code": "[redacted]",
        "code_verifier": "TXAkIpwx0U80Z3nwAtLH2nL48Ouk4CLYVtbIB2L8VUg",
        "redirect_uri": "https://localhost:7170/callback/login/local",
        "client_id": "mvcClientE",
        "client_secret": "[redacted]"
      }.
info: OpenIddict.Client.OpenIddictClientDispatcher[0]
      The token response returned by https://localhost:7179/connect/token was successfully extracted: {
        "access_token": "[redacted]",
        "token_type": "Bearer",
        "expires_in": 3599,
        "scope": "openid email profile",
        "id_token": "[redacted]"
      }.
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.LogicalHandler[100]
      Start processing HTTP request GET https://localhost:7179/connect/userinfo
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.ClientHandler[100]
      Sending HTTP request GET https://localhost:7179/connect/userinfo
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.ClientHandler[101]
      Received HTTP response headers after 45.6701ms - 200
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.LogicalHandler[101]
      End processing HTTP request after 45.8968ms - 200
info: OpenIddict.Client.OpenIddictClientDispatcher[0]
      The userinfo request was successfully sent to https://localhost:7179/connect/userinfo: {}.
info: OpenIddict.Client.OpenIddictClientDispatcher[0]
      The userinfo response returned by https://localhost:7179/connect/userinfo was successfully extracted: {
        "sub": "4274c9f5-882c-4833-9cd5-574da5a1c6a7",
        "email": "a@b.com",
        "email_verified": false
      }.
info: OpenIddict.Client.OpenIddictClientDispatcher[0]
      The redirection request was successfully validated.
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.LogicalHandler[100]
      Start processing HTTP request POST https://localhost:7179/connect/introspect
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.ClientHandler[100]
      Sending HTTP request POST https://localhost:7179/connect/introspect
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.ClientHandler[101]
      Received HTTP response headers after 8.2996ms - 400
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.LogicalHandler[101]
      End processing HTTP request after 8.4536ms - 400
info: OpenIddict.Client.OpenIddictClientDispatcher[0]
      The token request was successfully sent to https://localhost:7179/connect/introspect: {
        "token_type_hint": "access_token",
        "client_id": "mvcClientE",
        "client_secret": "[redacted]"
      }.
info: OpenIddict.Client.OpenIddictClientDispatcher[0]
      The token response returned by https://localhost:7179/connect/introspect was successfully extracted: {
        "error": "invalid_request",
        "error_description": "The mandatory 'token' parameter is missing.",
        "error_uri": "https://documentation.openiddict.com/errors/ID2029"
      }.
info: OpenIddict.Client.OpenIddictClientDispatcher[0]
      The introspection request was rejected by the remote authorization server: {
        "error": "invalid_request",
        "error_description": "The mandatory 'token' parameter is missing.",
        "error_uri": "https://documentation.openiddict.com/errors/ID2029"
      }.
feededit commented 1 week ago

server

info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The response was successfully returned as a JSON document: {
        "access_token": "[redacted]",
        "token_type": "Bearer",
        "expires_in": 3599,
        "scope": "openid email profile",
        "id_token": "[redacted]"
      }.
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The request URI matched a server endpoint: Userinfo.
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The userinfo request was successfully extracted: {
        "access_token": "[redacted]"
      }.
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[@__key_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30']
      SELECT TOP(1) [o].[Id], [o].[ApplicationId], [o].[AuthorizationId], [o].[ConcurrencyToken], [o].[CreationDate], [o].[ExpirationDate], [o].[Payload], [o].[Properties], [o].[RedemptionDate], [o].[ReferenceId], [o].[Status], [o].[Subject], [o].[Type], [o0].[Id], [o0].[ApplicationType], [o0].[ClientId], [o0].[ClientSecret], [o0].[ClientType], [o0].[ConcurrencyToken], [o0].[ConsentType], [o0].[DisplayName], [o0].[DisplayNames], [o0].[JsonWebKeySet], [o0].[Permissions], [o0].[PostLogoutRedirectUris], [o0].[Properties], [o0].[RedirectUris], [o0].[Requirements], [o0].[Settings], [o1].[Id], [o1].[ApplicationId], [o1].[ConcurrencyToken], [o1].[CreationDate], [o1].[Properties], [o1].[Scopes], [o1].[Status], [o1].[Subject], [o1].[Type]
      FROM [OpenIddictTokens] AS [o]
      LEFT JOIN [OpenIddictApplications] AS [o0] ON [o].[ApplicationId] = [o0].[Id]
      LEFT JOIN [OpenIddictAuthorizations] AS [o1] ON [o].[AuthorizationId] = [o1].[Id]
      WHERE [o].[Id] = @__key_0
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The userinfo request was successfully validated.
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[@__p_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30']
      SELECT TOP(1) [a].[Id], [a].[AccessFailedCount], [a].[ConcurrencyStamp], [a].[Email], [a].[EmailConfirmed], [a].[LockoutEnabled], [a].[LockoutEnd], [a].[NormalizedEmail], [a].[NormalizedUserName], [a].[PasswordHash], [a].[PhoneNumber], [a].[PhoneNumberConfirmed], [a].[SecurityStamp], [a].[TwoFactorEnabled], [a].[UserName]
      FROM [AspNetUsers] AS [a]
      WHERE [a].[Id] = @__p_0
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The request URI matched a server endpoint: Introspection.
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The introspection request was successfully extracted: {
        "token_type_hint": "access_token",
        "client_id": "mvcClientE",
        "client_secret": "[redacted]"
      }.
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The introspection request was rejected because the mandatory 'token' parameter was missing.
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The response was successfully returned as a JSON document: {
        "error": "invalid_request",
        "error_description": "The mandatory 'token' parameter is missing.",
        "error_uri": "https://documentation.openiddict.com/errors/ID2029"
      }.
kevinchalet commented 1 week ago

The token isn't attached to the introspection request, most likely because var accessToken = await HttpContext.GetTokenAsync("access_token"); returns null.

(I'll add a null check to make debugging easier, since it's not legal to send an introspection request without a token attached anyway)

kevinchalet commented 1 week ago

(I'll add a null check to make debugging easier, since it's not legal to send an introspection request without a token attached anyway)

https://github.com/openiddict/openiddict-core/issues/2218

feededit commented 1 week ago

The token isn't attached to the introspection request, most likely because var accessToken = await HttpContext.GetTokenAsync("access_token"); returns null.

The fundamental problem is that the code below does not read the access token. What is the correct code to read the access token ?

     var accessToken = await HttpContext.GetTokenAsync("access_token");
feededit commented 1 week ago

BackchannelAccessToken is read. But it was rejected by the server.

                    var accessTokenClaim = await HttpContext.GetTokenAsync(OpenIddictClientAspNetCoreConstants.Tokens.BackchannelAccessToken);

error message

unauthorized_client Error description: The introspection request was rejected by the remote server.
kevinchalet commented 1 week ago

But it was rejected by the server. error message

Server logs?

feededit commented 1 week ago

I'm not sure. Please refer to the code below

message

Unexpected Error: 
ProtocolException - 
An error occurred while introspecting a token. 
Error: unauthorized_client Error 
description: The introspection request was rejected by the remote server. 
Error URI: https://documentation.openiddict.com/errors/ID2146
            if (User?.Identity is { IsAuthenticated: true })
            {
                user = User.Identity.Name;

                try
                {
                    var accessTokenClaim = await HttpContext.GetTokenAsync(OpenIddictClientAspNetCoreConstants.Tokens.BackchannelAccessToken);

                    if (accessTokenClaim != null && !string.IsNullOrWhiteSpace(accessTokenClaim))
                    {
                        try
                        {
                            _ = await _service.IntrospectTokenAsync(new()
                            {
                                Token = accessTokenClaim,
                                TokenTypeHint = TokenTypeHints.AccessToken
                            });
                        }
                        catch (HttpRequestException ex)
                        {
                            user = $"Network Error: {ex.Message}";
                        }
                        catch (TimeoutException ex)
                        {
                            user = "Error: Service timeout";
                        }
                        catch (Exception ex) 
                        {
                            user = $"Unexpected Error: {ex.GetType().Name} - {ex.Message}";
                        }
                    }
                    else
                    {
                        user = "Error: Access token not found";
                    }
                }
                catch (Exception ex)
                {
                    user = $"Critical Error: {ex.GetType().Name} - {ex.Message}";
                }
            }

            ViewBag.User = user;
kevinchalet commented 1 week ago

No, I really meant the logs of the authorization server: the OAuth 2.0 introspection specification requires returning active = false instead of a proper OAuth 2.0 error when the introspection request cannot be served to reduce token scanning attacks (which is very stupid, IMHO): this means that you'll never get any useful information just by looking at the client logs: you need the server logs to investigate further.

feededit commented 1 week ago

server

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[@__identifier_0='?' (Size = 100)], CommandType='Text', CommandTimeout='30']
      SELECT TOP(1) [o].[Id], [o].[ApplicationType], [o].[ClientId], [o].[ClientSecret], [o].[ClientType], [o].[ConcurrencyToken], [o].[ConsentType], [o].[DisplayName], [o].[DisplayNames], [o].[JsonWebKeySet], [o].[Permissions], [o].[PostLogoutRedirectUris], [o].[Properties], [o].[RedirectUris], [o].[Requirements], [o].[Settings]
      FROM [OpenIddictApplications] AS [o]
      WHERE [o].[ClientId] = @__identifier_0
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[@__identifier_0='?' (Size = 100)], CommandType='Text', CommandTimeout='30']
      SELECT TOP(1) [o].[Id], [o].[ApplicationId], [o].[AuthorizationId], [o].[ConcurrencyToken], [o].[CreationDate], [o].[ExpirationDate], [o].[Payload], [o].[Properties], [o].[RedemptionDate], [o].[ReferenceId], [o].[Status], [o].[Subject], [o].[Type], [o0].[Id], [o0].[ApplicationType], [o0].[ClientId], [o0].[ClientSecret], [o0].[ClientType], [o0].[ConcurrencyToken], [o0].[ConsentType], [o0].[DisplayName], [o0].[DisplayNames], [o0].[JsonWebKeySet], [o0].[Permissions], [o0].[PostLogoutRedirectUris], [o0].[Properties], [o0].[RedirectUris], [o0].[Requirements], [o0].[Settings], [o1].[Id], [o1].[ApplicationId], [o1].[ConcurrencyToken], [o1].[CreationDate], [o1].[Properties], [o1].[Scopes], [o1].[Status], [o1].[Subject], [o1].[Type]
      FROM [OpenIddictTokens] AS [o]
      LEFT JOIN [OpenIddictApplications] AS [o0] ON [o].[ApplicationId] = [o0].[Id]
      LEFT JOIN [OpenIddictAuthorizations] AS [o1] ON [o].[AuthorizationId] = [o1].[Id]
      WHERE [o].[ReferenceId] = @__identifier_0
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The token request was successfully validated.
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[@__p_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30']
      SELECT TOP(1) [a].[Id], [a].[AccessFailedCount], [a].[ConcurrencyStamp], [a].[Email], [a].[EmailConfirmed], [a].[LockoutEnabled], [a].[LockoutEnd], [a].[NormalizedEmail], [a].[NormalizedUserName], [a].[PasswordHash], [a].[PhoneNumber], [a].[PhoneNumberConfirmed], [a].[SecurityStamp], [a].[TwoFactorEnabled], [a].[UserName]
      FROM [AspNetUsers] AS [a]
      WHERE [a].[Id] = @__p_0
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[@__userId_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30']
      SELECT [a0].[Name]
      FROM [AspNetUserRoles] AS [a]
      INNER JOIN [AspNetRoles] AS [a0] ON [a].[RoleId] = [a0].[Id]
      WHERE [a].[UserId] = @__userId_0
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[@p12='?' (Size = 450), @p0='?' (Size = 450), @p1='?' (Size = 450), @p2='?' (Size = 50), @p13='?' (Size = 50), @p3='?' (DbType = DateTime2), @p4='?' (DbType = DateTime2), @p5='?' (Size = 4000), @p6='?' (Size = 4000), @p7='?' (DbType = DateTime2), @p8='?' (Size = 100), @p9='?' (Size = 50), @p10='?' (Size = 400), @p11='?' (Size = 50)], CommandType='Text', CommandTimeout='30']
      SET IMPLICIT_TRANSACTIONS OFF;
      SET NOCOUNT ON;
      UPDATE [OpenIddictTokens] SET [ApplicationId] = @p0, [AuthorizationId] = @p1, [ConcurrencyToken] = @p2, [CreationDate] = @p3, [ExpirationDate] = @p4, [Payload] = @p5, [Properties] = @p6, [RedemptionDate] = @p7, [ReferenceId] = @p8, [Status] = @p9, [Subject] = @p10, [Type] = @p11
      OUTPUT 1
      WHERE [Id] = @p12 AND [ConcurrencyToken] = @p13;
info: OpenIddict.Core.OpenIddictTokenManager[0]
      The token 'f88621d8-aaf7-4db8-9d8f-019cc33a944b' was successfully marked as redeemed.
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (2ms) [Parameters=[@p0='?' (Size = 450), @p1='?' (Size = 450), @p2='?' (Size = 450), @p3='?' (Size = 50), @p4='?' (DbType = DateTime2), @p5='?' (DbType = DateTime2), @p6='?' (Size = 4000), @p7='?' (Size = 4000), @p8='?' (DbType = DateTime2), @p9='?' (Size = 100), @p10='?' (Size = 50), @p11='?' (Size = 400), @p12='?' (Size = 50)], CommandType='Text', CommandTimeout='30']
      SET IMPLICIT_TRANSACTIONS OFF;
      SET NOCOUNT ON;
      INSERT INTO [OpenIddictTokens] ([Id], [ApplicationId], [AuthorizationId], [ConcurrencyToken], [CreationDate], [ExpirationDate], [Payload], [Properties], [RedemptionDate], [ReferenceId], [Status], [Subject], [Type])
      VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[@p0='?' (Size = 450), @p1='?' (Size = 450), @p2='?' (Size = 450), @p3='?' (Size = 50), @p4='?' (DbType = DateTime2), @p5='?' (DbType = DateTime2), @p6='?' (Size = 4000), @p7='?' (Size = 4000), @p8='?' (DbType = DateTime2), @p9='?' (Size = 100), @p10='?' (Size = 50), @p11='?' (Size = 400), @p12='?' (Size = 50)], CommandType='Text', CommandTimeout='30']
      SET IMPLICIT_TRANSACTIONS OFF;
      SET NOCOUNT ON;
      INSERT INTO [OpenIddictTokens] ([Id], [ApplicationId], [AuthorizationId], [ConcurrencyToken], [CreationDate], [ExpirationDate], [Payload], [Properties], [RedemptionDate], [ReferenceId], [Status], [Subject], [Type])
      VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12);
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The response was successfully returned as a JSON document: {
        "access_token": "[redacted]",
        "token_type": "Bearer",
        "expires_in": 3599,
        "scope": "openid email profile",
        "id_token": "[redacted]"
      }.
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The request URI matched a server endpoint: Userinfo.
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The userinfo request was successfully extracted: {
        "access_token": "[redacted]"
      }.
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (3ms) [Parameters=[@__key_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30']
      SELECT TOP(1) [o].[Id], [o].[ApplicationId], [o].[AuthorizationId], [o].[ConcurrencyToken], [o].[CreationDate], [o].[ExpirationDate], [o].[Payload], [o].[Properties], [o].[RedemptionDate], [o].[ReferenceId], [o].[Status], [o].[Subject], [o].[Type], [o0].[Id], [o0].[ApplicationType], [o0].[ClientId], [o0].[ClientSecret], [o0].[ClientType], [o0].[ConcurrencyToken], [o0].[ConsentType], [o0].[DisplayName], [o0].[DisplayNames], [o0].[JsonWebKeySet], [o0].[Permissions], [o0].[PostLogoutRedirectUris], [o0].[Properties], [o0].[RedirectUris], [o0].[Requirements], [o0].[Settings], [o1].[Id], [o1].[ApplicationId], [o1].[ConcurrencyToken], [o1].[CreationDate], [o1].[Properties], [o1].[Scopes], [o1].[Status], [o1].[Subject], [o1].[Type]
      FROM [OpenIddictTokens] AS [o]
      LEFT JOIN [OpenIddictApplications] AS [o0] ON [o].[ApplicationId] = [o0].[Id]
      LEFT JOIN [OpenIddictAuthorizations] AS [o1] ON [o].[AuthorizationId] = [o1].[Id]
      WHERE [o].[Id] = @__key_0
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The userinfo request was successfully validated.
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[@__p_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30']
      SELECT TOP(1) [a].[Id], [a].[AccessFailedCount], [a].[ConcurrencyStamp], [a].[Email], [a].[EmailConfirmed], [a].[LockoutEnabled], [a].[LockoutEnd], [a].[NormalizedEmail], [a].[NormalizedUserName], [a].[PasswordHash], [a].[PhoneNumber], [a].[PhoneNumberConfirmed], [a].[SecurityStamp], [a].[TwoFactorEnabled], [a].[UserName]
      FROM [AspNetUsers] AS [a]
      WHERE [a].[Id] = @__p_0
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The request URI matched a server endpoint: Introspection.
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The introspection request was successfully extracted: {
        "token": "[redacted]",
        "token_type_hint": "access_token",
        "client_id": "mvcClientE",
        "client_secret": "[redacted]"
      }.
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[@__identifier_0='?' (Size = 100)], CommandType='Text', CommandTimeout='30']
      SELECT TOP(1) [o].[Id], [o].[ApplicationType], [o].[ClientId], [o].[ClientSecret], [o].[ClientType], [o].[ConcurrencyToken], [o].[ConsentType], [o].[DisplayName], [o].[DisplayNames], [o].[JsonWebKeySet], [o].[Permissions], [o].[PostLogoutRedirectUris], [o].[Properties], [o].[RedirectUris], [o].[Requirements], [o].[Settings]
      FROM [OpenIddictApplications] AS [o]
      WHERE [o].[ClientId] = @__identifier_0
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[@__key_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30']
      SELECT TOP(1) [o].[Id], [o].[ApplicationId], [o].[AuthorizationId], [o].[ConcurrencyToken], [o].[CreationDate], [o].[ExpirationDate], [o].[Payload], [o].[Properties], [o].[RedemptionDate], [o].[ReferenceId], [o].[Status], [o].[Subject], [o].[Type], [o0].[Id], [o0].[ApplicationType], [o0].[ClientId], [o0].[ClientSecret], [o0].[ClientType], [o0].[ConcurrencyToken], [o0].[ConsentType], [o0].[DisplayName], [o0].[DisplayNames], [o0].[JsonWebKeySet], [o0].[Permissions], [o0].[PostLogoutRedirectUris], [o0].[Properties], [o0].[RedirectUris], [o0].[Requirements], [o0].[Settings], [o1].[Id], [o1].[ApplicationId], [o1].[ConcurrencyToken], [o1].[CreationDate], [o1].[Properties], [o1].[Scopes], [o1].[Status], [o1].[Subject], [o1].[Type]
      FROM [OpenIddictTokens] AS [o]
      LEFT JOIN [OpenIddictApplications] AS [o0] ON [o].[ApplicationId] = [o0].[Id]
      LEFT JOIN [OpenIddictAuthorizations] AS [o1] ON [o].[AuthorizationId] = [o1].[Id]
      WHERE [o].[Id] = @__key_0
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The introspection request was rejected because the application 'mvcClientE' was not allowed to use the introspection endpoint.
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The response was successfully returned as a JSON document: {
        "error": "unauthorized_client",
        "error_description": "This client application is not allowed to use the introspection endpoint.",
        "error_uri": "https://documentation.openiddict.com/errors/ID2075"
      }.
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (6ms) [Parameters=[@__p_1='?' (DbType = Int32), @__date_0='?' (DbType = DateTime2)], CommandType='Text', CommandTimeout='30']
      DELETE FROM [o]
      FROM [OpenIddictTokens] AS [o]
      WHERE [o].[Id] IN (
          SELECT TOP(@__p_1) [o0].[Id]
          FROM [OpenIddictTokens] AS [o0]
          LEFT JOIN [OpenIddictAuthorizations] AS [o1] ON [o0].[AuthorizationId] = [o1].[Id]
          WHERE [o0].[CreationDate] < @__date_0 AND ((([o0].[Status] <> N'inactive' OR [o0].[Status] IS NULL) AND ([o0].[Status] <> N'valid' OR [o0].[Status] IS NULL)) OR ([o1].[Id] IS NOT NULL AND ([o1].[Status] <> N'valid' OR [o1].[Status] IS NULL)) OR [o0].[ExpirationDate] < GETUTCDATE())
          ORDER BY [o0].[Id]
      )
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (4ms) [Parameters=[@__p_1='?' (DbType = Int32), @__date_0='?' (DbType = DateTime2)], CommandType='Text', CommandTimeout='30']
      DELETE FROM [o]
      FROM [OpenIddictAuthorizations] AS [o]
      WHERE [o].[Id] IN (
          SELECT TOP(@__p_1) [o0].[Id]
          FROM [OpenIddictAuthorizations] AS [o0]
          WHERE [o0].[CreationDate] < @__date_0 AND ([o0].[Status] <> N'valid' OR [o0].[Status] IS NULL OR ([o0].[Type] = N'ad-hoc' AND NOT EXISTS (
              SELECT 1
              FROM [OpenIddictTokens] AS [o1]
              WHERE [o0].[Id] = [o1].[AuthorizationId])))
          ORDER BY [o0].[Id]
      )

client

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (4ms) [Parameters=[@__identifier_0='?' (Size = 100)], CommandType='Text', CommandTimeout='30']
      SELECT TOP(1) [o].[Id], [o].[ApplicationId], [o].[AuthorizationId], [o].[ConcurrencyToken], [o].[CreationDate], [o].[ExpirationDate], [o].[Payload], [o].[Properties], [o].[RedemptionDate], [o].[ReferenceId], [o].[Status], [o].[Subject], [o].[Type], [o0].[Id], [o0].[ApplicationType], [o0].[ClientId], [o0].[ClientSecret], [o0].[ClientType], [o0].[ConcurrencyToken], [o0].[ConsentType], [o0].[DisplayName], [o0].[DisplayNames], [o0].[JsonWebKeySet], [o0].[Permissions], [o0].[PostLogoutRedirectUris], [o0].[Properties], [o0].[RedirectUris], [o0].[Requirements], [o0].[Settings], [o1].[Id], [o1].[ApplicationId], [o1].[ConcurrencyToken], [o1].[CreationDate], [o1].[Properties], [o1].[Scopes], [o1].[Status], [o1].[Subject], [o1].[Type]
      FROM [OpenIddictTokens] AS [o]
      LEFT JOIN [OpenIddictApplications] AS [o0] ON [o].[ApplicationId] = [o0].[Id]
      LEFT JOIN [OpenIddictAuthorizations] AS [o1] ON [o].[AuthorizationId] = [o1].[Id]
      WHERE [o].[ReferenceId] = @__identifier_0
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[@p12='?' (Size = 450), @p0='?' (Size = 450), @p1='?' (Size = 450), @p2='?' (Size = 50), @p13='?' (Size = 50), @p3='?' (DbType = DateTime2), @p4='?' (DbType = DateTime2), @p5='?' (Size = 4000), @p6='?' (Size = 4000), @p7='?' (DbType = DateTime2), @p8='?' (Size = 100), @p9='?' (Size = 50), @p10='?' (Size = 400), @p11='?' (Size = 50)], CommandType='Text', CommandTimeout='30']
      SET IMPLICIT_TRANSACTIONS OFF;
      SET NOCOUNT ON;
      UPDATE [OpenIddictTokens] SET [ApplicationId] = @p0, [AuthorizationId] = @p1, [ConcurrencyToken] = @p2, [CreationDate] = @p3, [ExpirationDate] = @p4, [Payload] = @p5, [Properties] = @p6, [RedemptionDate] = @p7, [ReferenceId] = @p8, [Status] = @p9, [Subject] = @p10, [Type] = @p11
      OUTPUT 1
      WHERE [Id] = @p12 AND [ConcurrencyToken] = @p13;
info: OpenIddict.Core.OpenIddictTokenManager[0]
      The token 'df374c88-2f62-4ab2-a963-0754d7e1cd78' was successfully marked as redeemed.
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.LogicalHandler[100]
      Start processing HTTP request POST https://localhost:7179/connect/token
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.ClientHandler[100]
      Sending HTTP request POST https://localhost:7179/connect/token
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.ClientHandler[101]
      Received HTTP response headers after 458.847ms - 200
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.LogicalHandler[101]
      End processing HTTP request after 459.0244ms - 200
info: OpenIddict.Client.OpenIddictClientDispatcher[0]
      The token request was successfully sent to https://localhost:7179/connect/token: {
        "grant_type": "authorization_code",
        "code": "[redacted]",
        "code_verifier": "vzMiHisD_xcsssxQSapWTZhTq-40Zycsw3GLA0WMEts",
        "redirect_uri": "https://localhost:7170/callback/login/local",
        "client_id": "mvcClientE",
        "client_secret": "[redacted]"
      }.
info: OpenIddict.Client.OpenIddictClientDispatcher[0]
      The token response returned by https://localhost:7179/connect/token was successfully extracted: {
        "access_token": "[redacted]",
        "token_type": "Bearer",
        "expires_in": 3599,
        "scope": "openid email profile",
        "id_token": "[redacted]"
      }.
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.LogicalHandler[100]
      Start processing HTTP request GET https://localhost:7179/connect/userinfo
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.ClientHandler[100]
      Sending HTTP request GET https://localhost:7179/connect/userinfo
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.ClientHandler[101]
      Received HTTP response headers after 45.1974ms - 200
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.LogicalHandler[101]
      End processing HTTP request after 45.3584ms - 200
info: OpenIddict.Client.OpenIddictClientDispatcher[0]
      The userinfo request was successfully sent to https://localhost:7179/connect/userinfo: {}.
info: OpenIddict.Client.OpenIddictClientDispatcher[0]
      The userinfo response returned by https://localhost:7179/connect/userinfo was successfully extracted: {
        "sub": "d0455311-82ef-4c0b-ba48-2e4832620178",
        "email": "a@b.com",
        "email_verified": false
      }.
info: OpenIddict.Client.OpenIddictClientDispatcher[0]
      The redirection request was successfully validated.
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.LogicalHandler[100]
      Start processing HTTP request POST https://localhost:7179/connect/introspect
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.ClientHandler[100]
      Sending HTTP request POST https://localhost:7179/connect/introspect
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.ClientHandler[101]
      Received HTTP response headers after 23.6236ms - 400
info: System.Net.Http.HttpClient.OpenIddict.Client.SystemNetHttp:gSG7HgCxenpIiI1KLWE4LEnE7oWNX5JYnL-EVl6s2XA.LogicalHandler[101]
      End processing HTTP request after 23.7685ms - 400
info: OpenIddict.Client.OpenIddictClientDispatcher[0]
      The token request was successfully sent to https://localhost:7179/connect/introspect: {
        "token": "[redacted]",
        "token_type_hint": "access_token",
        "client_id": "mvcClientE",
        "client_secret": "[redacted]"
      }.
info: OpenIddict.Client.OpenIddictClientDispatcher[0]
      The token response returned by https://localhost:7179/connect/introspect was successfully extracted: {
        "error": "unauthorized_client",
        "error_description": "This client application is not allowed to use the introspection endpoint.",
        "error_uri": "https://documentation.openiddict.com/errors/ID2075"
      }.
info: OpenIddict.Client.OpenIddictClientDispatcher[0]
      The introspection request was rejected by the remote authorization server: {
        "error": "unauthorized_client",
        "error_description": "This client application is not allowed to use the introspection endpoint.",
        "error_uri": "https://documentation.openiddict.com/errors/ID2075"
      }.
kevinchalet commented 1 week ago
info: OpenIddict.Server.OpenIddictServerDispatcher[0]
      The response was successfully returned as a JSON document: {
        "error": "unauthorized_client",
        "error_description": "This client application is not allowed to use the introspection endpoint.",
        "error_uri": "https://documentation.openiddict.com/errors/ID2075"
      }.

You didn't add the introspection endpoint permission...

feededit commented 1 week ago

You didn't add the introspection endpoint permission...

I won't know how unless you tell me.

kevinchalet commented 1 week ago

I won't know how unless you tell me.

👇🏻

and update your client applications to give them the OpenIddictConstants.Permissions.Endpoints.Introspection permission.

https://documentation.openiddict.com/configuration/application-permissions

feededit commented 1 week ago

It works fine. Nice work, as always. Please close this issue.

feededit commented 1 week ago

4.x client can't connect to 5.X server?

An exception occurred while iterating over the results of a query for context type 'EutClient.Data.ApplicationDbContext'.
Microsoft.Data.SqlClient.SqlException (0x80131904): Column name 'Type' is invalid.
   at Microsoft.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__188_0(Task`1 result)
   at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
   at System.Threading.Tasks.Task.<>c.<.cctor>b__272_0(Object obj)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
kevinchalet commented 1 week ago

Nice work, as always.

Well, to be honest, it's a workaround, but it should work reliably for the time being 😄

4.x client can't connect to 5.X server?

It can, but you can't use the OpenIddict 5.x packages with a database creating using OpenIddict 4x if it hasn't been updated to use the 5.x schema using EF Core migrations.

feededit commented 6 days ago

Let's make the client 5.x.