Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
1 task done
feededit opened this issue Oct 30, 2024 · 47 comments
Open
1 task done

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

feededit opened this issue Oct 30, 2024 · 47 comments
Labels

Comments

@feededit
Copy link

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

  • I confirm I'm a sponsor or a contributor

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
Copy link
Member

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 openiddict/openiddict-core#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
Copy link
Author

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
Copy link
Member

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
Copy link
Author

feededit commented Nov 1, 2024

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
Copy link
Member

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
Copy link
Author

feededit commented Nov 1, 2024

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
Copy link
Author

feededit commented Nov 1, 2024

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
Copy link
Author

feededit commented Nov 2, 2024

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
Copy link
Member

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
Copy link
Author

feededit commented Nov 2, 2024

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

@feededit
Copy link
Author

feededit commented Nov 2, 2024

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
Copy link
Member

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
Copy link
Author

feededit commented Nov 2, 2024

I am in testing. So it's localhost.

@kevinchalet
Copy link
Member

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
Copy link
Author

feededit commented Nov 2, 2024

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

@kevinchalet
Copy link
Member

My pleasure! ❤️

@feededit
Copy link
Author

feededit commented Nov 2, 2024

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
Copy link
Member

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
Copy link
Author

feededit commented Nov 2, 2024

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
Copy link
Member

kevinchalet commented Nov 2, 2024

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
Copy link
Author

feededit commented Nov 3, 2024

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
Copy link
Member

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
Copy link
Author

feededit commented Nov 3, 2024

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
Copy link
Author

feededit commented Nov 3, 2024

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
Copy link
Member

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
Copy link
Author

feededit commented Nov 3, 2024

"connect/introspect"

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

@feededit
Copy link
Author

feededit commented Nov 3, 2024

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
Copy link
Member

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
Copy link
Author

feededit commented Nov 3, 2024

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
Copy link
Member

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
Copy link
Author

feededit commented Nov 4, 2024

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
Copy link
Author

feededit commented Nov 4, 2024

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
Copy link
Member

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
Copy link
Member

(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)

openiddict/openiddict-core#2218

@feededit
Copy link
Author

feededit commented Nov 4, 2024

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
Copy link
Author

feededit commented Nov 4, 2024

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
Copy link
Member

But it was rejected by the server. error message

Server logs?

@feededit
Copy link
Author

feededit commented Nov 4, 2024

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
Copy link
Member

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
Copy link
Author

feededit commented Nov 4, 2024

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
Copy link
Member

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
Copy link
Author

feededit commented Nov 4, 2024

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

I won't know how unless you tell me.

@kevinchalet
Copy link
Member

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
Copy link
Author

feededit commented Nov 4, 2024

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

@feededit
Copy link
Author

feededit commented Nov 6, 2024

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
Copy link
Member

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
Copy link
Author

feededit commented Nov 6, 2024

Let's make the client 5.x.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants