We have a desktop application which resolves a random port, and uses it each time it calls login, to extract the token, and on logout to confirm successful browser logout. We have configured an open id application for this client app, with redirect uri, and post logout redirect uri (http//:127.0.0.1).
After upgrading from abp 8.2.2 to 9.1.1, login still works fine, howeover, logouts started coming back with 400 response code. Logs show the post logout redirect uri was rejected. Logging in/out using the portal still works fine.
OpenId Application Post LogOut Redirect Uri: http://127.0.0.1 Http Request post_logout_redirect_uri: http://127.0.0.1:31270/ Response Code: 400 Issue From Logs: The end session request was rejected because the specified post_logout_redirect_uri was invalid: http://127.0.0.1:31270/.
10 Answer(s)
-
0
To resolve the 400 response code for logout after upgrading to ABP 9.1.1, you need to ensure that the exact post_logout_redirect_uri (including port) used in the logout request is registered in your OpenIddict application's configuration.
In ABP 9.x (and recent OpenIddict versions), the post_logout_redirect_uri must match exactly (including scheme, host, and port) with one of the URIs registered for the client application. Registering only http://127.0.0.1 is not sufficient if your application uses random ports (e.g., http://127.0.0.1:31270/).
Solution:
- Register all possible redirect URIs with the port your desktop app uses, or use a wildcard if supported (OpenIddict does not support wildcards for security reasons).
- If your app uses random ports, you must pre-register each possible port, or use a fixed port for the redirect URI.
How to Register Multiple Redirect URIs:
- In your OpenIddict client configuration (typically in
OpenIddictDataSeedContributor.cs
), add each allowed post logout redirect URI:
builder.WithPostLogoutRedirectUris( "http://127.0.0.1:31270/", "http://127.0.0.1:31271/", // Add all ports your app may use );
- Alternatively, configure your desktop app to use a fixed port and register only that URI.
References:
This strict matching is a security requirement in recent OpenIddict versions and is enforced by ABP 9.x. Adjust your client registration or application logic accordingly to resolve the 400 error.
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
-
0
hi
Can you share the debug logs of your AuthServer website?
liming.ma@volosoft.com
https://abp.io/support/questions/8622/How-to-enable-Debug-logs-for-troubleshoot-problems
Thanks.
-
0
[maliming] said: hi
Can you share the debug logs of your AuthServer website?
liming.ma@volosoft.com
https://abp.io/support/questions/8622/How-to-enable-Debug-logs-for-troubleshoot-problems
Thanks.
I sent you the logout logs.
-
0
-
0
[maliming] said: hi
The 400 error :
The end session request was rejected because the specified post_logout_redirect_uri was invalid: http://127.0.0.1:26908/
Can you add
http://127.0.0.1:26908/
to your application's post_logout_redirect_uri?Like I mentioned earlier, the desktop app picks the port at random. Therefore, it is not feasible to add them manually to post logout redirect uris. Our inquiry is: what caused this breaking change, as this was working fine before the upgrade? How do make sure our existing, deployed, desktop apps continue to work as expected with the new backend.
-
0
hi
Add
MyAbpValidatePostLogoutRedirectUriParameter
andMyAbpValidateClientPostLogoutRedirectUri
to support the wildcard domain.public override void PreConfigureServices(ServiceConfigurationContext context) { PreConfigure<OpenIddictServerBuilder>(builder => { builder.RemoveEventHandler(AbpValidatePostLogoutRedirectUriParameter.Descriptor); builder.AddEventHandler(MyAbpValidatePostLogoutRedirectUriParameter.Descriptor); builder.RemoveEventHandler(AbpValidateClientPostLogoutRedirectUri.Descriptor); builder.AddEventHandler(MyAbpValidateClientPostLogoutRedirectUri.Descriptor); }); PreConfigure<AbpOpenIddictWildcardDomainOptions>(options => { options.EnableWildcardDomainSupport = true; options.WildcardDomainsFormat.Add("http://127.0.0.1:{0}"); }); }
using Microsoft.Extensions.Options; using OpenIddict.Abstractions; using OpenIddict.Server; using Volo.Abp; using Volo.Abp.Http; using Volo.Abp.OpenIddict.WildcardDomains; using Volo.Abp.Text.Formatting; namespace OpenIddict.Demo.Server; public class MyAbpValidatePostLogoutRedirectUriParameter : AbpOpenIddictWildcardDomainBase<MyAbpValidatePostLogoutRedirectUriParameter, OpenIddictServerHandlers.Session.ValidatePostLogoutRedirectUriParameter, OpenIddictServerEvents.ValidateEndSessionRequestContext> { public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder<OpenIddictServerEvents.ValidateEndSessionRequestContext>() .UseSingletonHandler<MyAbpValidatePostLogoutRedirectUriParameter>() .SetOrder(int.MinValue + 100_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); public MyAbpValidatePostLogoutRedirectUriParameter(IOptions<AbpOpenIddictWildcardDomainOptions> wildcardDomainsOptions) : base(wildcardDomainsOptions, new OpenIddictServerHandlers.Session.ValidatePostLogoutRedirectUriParameter()) { } public override async ValueTask HandleAsync(OpenIddictServerEvents.ValidateEndSessionRequestContext context) { Check.NotNull(context, nameof(context)); if (string.IsNullOrEmpty(context.PostLogoutRedirectUri) || await CheckWildcardDomainAsync(context.PostLogoutRedirectUri)) { return; } await OriginalHandler.HandleAsync(context); } protected override Task<bool> CheckWildcardDomainAsync(string url) { if (WildcardDomainOptions.WildcardDomainsFormat.IsNullOrEmpty()) { Logger.LogDebug("No wildcard domain format configured."); return Task.FromResult(false); } Logger.LogDebug("Checking wildcard domain for url: {url}", url); foreach (var domain in WildcardDomainOptions.WildcardDomainsFormat.Select(domainFormat => domainFormat.Replace("{0}", "*"))) { Logger.LogDebug("Checking wildcard domain format: {domain}", domain); if (UrlHelpers.IsSubdomainOf(url, domain)) { Logger.LogDebug("The url: {url} is a wildcard domain of: {domain}", url, domain); return Task.FromResult(true); } } foreach (var domain in WildcardDomainOptions.WildcardDomainsFormat) { Logger.LogDebug("Checking wildcard domain format: {domainFormat}", domain); var extractResult = FormattedStringValueExtracter.Extract(url, domain, ignoreCase: true); if (extractResult.IsMatch) { Logger.LogDebug("Wildcard domain found for url: {url}", url); return Task.FromResult(true); } } Logger.LogDebug("No wildcard domain found for url: {url}", url); return Task.FromResult(false); } } public class MyAbpValidateClientPostLogoutRedirectUri : AbpOpenIddictWildcardDomainBase<MyAbpValidateClientPostLogoutRedirectUri, OpenIddictServerHandlers.Session.ValidateClientPostLogoutRedirectUri, OpenIddictServerEvents.ValidateEndSessionRequestContext> { public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder<OpenIddictServerEvents.ValidateEndSessionRequestContext>() .AddFilter<OpenIddictServerHandlerFilters.RequireDegradedModeDisabled>() .AddFilter<OpenIddictServerHandlerFilters.RequirePostLogoutRedirectUriParameter>() .UseScopedHandler<MyAbpValidateClientPostLogoutRedirectUri>() .SetOrder(OpenIddictServerHandlers.Session.ValidatePostLogoutRedirectUriParameter.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); public MyAbpValidateClientPostLogoutRedirectUri( IOptions<AbpOpenIddictWildcardDomainOptions> wildcardDomainsOptions, IOpenIddictApplicationManager applicationManager) : base(wildcardDomainsOptions, new OpenIddictServerHandlers.Session.ValidateClientPostLogoutRedirectUri(applicationManager)) { OriginalHandler = new OpenIddictServerHandlers.Session.ValidateClientPostLogoutRedirectUri(applicationManager); } public override async ValueTask HandleAsync(OpenIddictServerEvents.ValidateEndSessionRequestContext context) { Check.NotNull(context, nameof(context)); Check.NotNullOrEmpty(context.PostLogoutRedirectUri, nameof(context.PostLogoutRedirectUri)); if (await CheckWildcardDomainAsync(context.PostLogoutRedirectUri)) { return; } await OriginalHandler.HandleAsync(context); } protected override Task<bool> CheckWildcardDomainAsync(string url) { if (WildcardDomainOptions.WildcardDomainsFormat.IsNullOrEmpty()) { Logger.LogDebug("No wildcard domain format configured."); return Task.FromResult(false); } Logger.LogDebug("Checking wildcard domain for url: {url}", url); foreach (var domain in WildcardDomainOptions.WildcardDomainsFormat.Select(domainFormat => domainFormat.Replace("{0}", "*"))) { Logger.LogDebug("Checking wildcard domain format: {domain}", domain); if (UrlHelpers.IsSubdomainOf(url, domain)) { Logger.LogDebug("The url: {url} is a wildcard domain of: {domain}", url, domain); return Task.FromResult(true); } } foreach (var domain in WildcardDomainOptions.WildcardDomainsFormat) { Logger.LogDebug("Checking wildcard domain format: {domainFormat}", domain); var extractResult = FormattedStringValueExtracter.Extract(url, domain, ignoreCase: true); if (extractResult.IsMatch) { Logger.LogDebug("Wildcard domain found for url: {url}", url); return Task.FromResult(true); } } Logger.LogDebug("No wildcard domain found for url: {url}", url); return Task.FromResult(false); } }
-
0
This did work. However, we still would like to understand what caused this breaking change. Especially since login allows the redirect, while logout doesn't. This doesn't feel right. Note that the wildcard domain was obviously already defined:
PreConfigure<AbpOpenIddictWildcardDomainOptions>(options => { options.EnableWildcardDomainSupport = true; options.WildcardDomainsFormat.Add("http://127.0.0.1:{0}"); });
-
0
Hi
we changed the wildcard domain check logic to make it more secure.
different port mean different domains.
Thanks.
-
0
[maliming] said:
Hi
we changed the wildcard domain check logic to make it more secure.
different port mean different domains.
Thanks.
... so why does login allow the redirect. Why is the logout redirect validation stricter than the login's, when the login in fact passes back the token?
-
0
Hi
Login request is sent by your client. But post logout url is return by authserver. We need to make sure user will not be redirected to malicious URL.
Thanks.