Skip to content
This repository has been archived by the owner on Feb 5, 2025. It is now read-only.

Dynamic OIDC configuration fails when logging out (at least for Google) #178

Closed
BillBaird opened this issue Aug 25, 2022 · 5 comments
Closed

Comments

@BillBaird
Copy link

BillBaird commented Aug 25, 2022

Which version of Duende IdentityServer are you using?

6.1.4

Which version of .NET are you using?

.net core 6.0

Describe the bug

When configuring the IdentityProviders table for Google as an OIDC provider, login works fine, but on logout I get the error InvalidOperationException: Cannot redirect to the end session endpoint, the configuration may be missing or invalid.

I have been successfully using Google as an OIDC provider in development by adding the configuration to the Startup.cs file. Recently, we decided to move the configuration to the IdentityProviders table. However, we are unable to get it to not throw the "InvalidOperationException: Cannot redirect to the end session endpoint, the configuration may be missing or invalid." exception on logout.

To Reproduce

  1. Take the DynamicProviders quickstart example. Update Startup.cs with your google ClientId and ClientSecret, make sure you have set a redirect URI to https://localhost:5001/signin-google as described in Startup.cs.
  2. Run IdentityServerHost (seeding the DB if necessary), then run MvcClient and verify that your google OIDC login is working as expected.
  3. Comment out the .AddGoogle lines in Startup.cs and create a dynamic configuration record in the IdentityProviders table in IdentityServer.db. The record should look like (with your Google ClientId and ClientSecret):
Scheme:  google
DisplayName:  Google (dynamic)
Enabled: 1
Type: oidc
Properties:
{
  "Authority": "https://accounts.google.com",
  "ClientId": "<your google client_id here>",
  "ClientSecret": "<your google secret here>",
  "Scope": "openid profile email",
  "SignInScheme": "idsrv.external"
}
  1. Make sure you have https://localhost:5001/federation/google/signin configured as a valid Google redirect URI.
  2. Run IdentityServerHost, then run MvcClient, log in using your "Google (dynamic)" configured OIDC provider. It should work.
  3. Log out from the MvcClient. You should see the error.

Expected behavior

The logout should complete just as it does when the Google OIDC configuration is in Startup.cs.

Log output/exception with stacktrace

System.InvalidOperationException: Cannot redirect to the end session endpoint, the configuration may be missing or invalid.
   at Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.SignOutAsync(AuthenticationProperties properties)
   at Microsoft.AspNetCore.Authentication.AuthenticationService.SignOutAsync(HttpContext context, String scheme, AuthenticationProperties properties)
   at Duende.IdentityServer.Hosting.IdentityServerAuthenticationService.SignOutAsync(HttpContext context, String scheme, AuthenticationProperties properties) in /_/src/IdentityServer/Hosting/IdentityServerAuthenticationService.cs:line 84
   at Microsoft.AspNetCore.Mvc.SignOutResult.ExecuteAsync(HttpContext httpContext)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|30_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Duende.IdentityServer.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events, IIssuerNameService issuerNameService, IBackChannelLogoutService backChannelLogoutService) in /_/src/IdentityServer/Hosting/IdentityServerMiddleware.cs:line 102
   at Duende.IdentityServer.Hosting.MutualTlsEndpointMiddleware.Invoke(HttpContext context, IAuthenticationSchemeProvider schemes) in /_/src/IdentityServer/Hosting/MutualTlsEndpointMiddleware.cs:line 94
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Duende.IdentityServer.Hosting.DynamicProviders.DynamicSchemeAuthenticationMiddleware.Invoke(HttpContext context) in /_/src/IdentityServer/Hosting/DynamicProviders/DynamicSchemes/DynamicSchemeAuthenticationMiddleware.cs:line 47
   at Duende.IdentityServer.Hosting.BaseUrlMiddleware.Invoke(HttpContext context) in /_/src/IdentityServer/Hosting/BaseUrlMiddleware.cs:line 27
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Discussion

Thankfully Google reported that I needed to have https://localhost:5001/federation/google/signin configured
in order for the login to work. This was not obvious as the example used "/signin-google". I tried setting
CallbackPath = "/signin-google" but that appears to be ignored for dynamic providers.

I recognize that this could be a configuration issue in the list of Properties in the IdentityProviders table, but
if it is, it is not obvious what I am doing wrong, nor could I find any documentation or examples of what it should be.
Since configuring Google with /federation/google/signin enabled the dynamic login to work, I see no reason that
logout should not work, which leads me to suspect this is a bug.

I was also wondering if something needed to be set on the clients OnRedirectToIdentityProviderForSignOut event,
but that feels wrong as the client should not have to assist with the basic logout capability.

@brockallen
Copy link
Member

Google does not support OIDC Logout, so you might have to add some code to prevent this, either in your logout page to know that scheme should not trigger OIDC SignOut, or you could add an event handler specifically to the google instance of the OIDC options:

https://docs.duendesoftware.com/identityserver/v6/ui/login/dynamicproviders/#customizing-openidconnectoptions

@BillBaird
Copy link
Author

Thanks for the quick response.

Yes, I was able to modify the controller used in the logout to not trigger the OIDC SignOut for Google.

Your comment on adding an event handler seems like a better solution, but I couldn't figure out how to do it. Tips or pointing me to a doc that shows how would be appreciated.

Finally, I imagine that many OIDC providers do not support OIDC logout. While it would be nice to know which ones do and which ones do not, since custom OIDC providers may always be around it would be nice if part of the dynamic IdentityProvider configuration would be whether the provider supports OIDC SignOut. I could see this as a well-known property in the IdentityProvider Properties configuration, or potentially its own column since even future non-oidc IdentityProviders would likely have the same concept. In that way, HttpContext.GetSchemeSupportsSignOutAsync could provide the configured answer for dynamic providers and not simply return true.

Could this modification be a feature request?

@brockallen
Copy link
Member

Your comment on adding an event handler seems like a better solution, but I couldn't figure out how to do it. Tips or pointing me to a doc that shows how would be appreciated.

I can add this as a backlog item for a sample.

I imagine that many OIDC providers do not support OIDC logout

Heresy! Just kidding.

Yea, ok it's possible. So perhaps a flag we have to prevent it internally?

The problem, though, is that the app-level logout logic when it calls SignOut with the scheme of the external provider, it expects as a side effect that the current response will be redirected. And if we prevent that, then what does we do? Not sure it's something we can overcome, since it's part of the design of the ASP.NET Core auth plumbing.

So then the only other option is to have some custom property that the logout page logic can consult to change its behavior. So maybe this is also part of the sample?

@BillBaird
Copy link
Author

Yes, a sample showing how to create, register, and use a custom handler would be very useful. Having the logout page consult a custom property seems a reasonable approach since the ASP.NET Core auth plumbing presents a challenge. Having that as part of the sample would be very useful.

Thanks for your support on this. I've found the samples to be very useful (not sure how I would have succeeded without them) and look forward to a future sample that shows a recommended way to handle this with dynamic providers.

@brockallen
Copy link
Member

Ok, thanks. I'll close this issue, since the new issue I created for the sample links here.

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

No branches or pull requests

2 participants