Open Closed

ABP 10.6 – OpenIddict Discovery Endpoint Returns 404/504 in DEV but Works Locally #10870


User avatar
0
Yaduraj.Shakti created

Problem statement

We have an ABP.io application using Angular + .NET Core + OpenIddict. After upgrading the application to ABP 10.6.0, the OpenID Connect discovery endpoint is not accessible in our DEV hosted environment.

The endpoint works correctly when running locally, but returns 404 or 504 in DEV.

Working locally

https://localhost:44350/.well-known/openid-configuration

This returns the expected OpenID Connect discovery document.

Failing in DEV

https://api-product.dev.<COMPANY_DOMAIN>/.well-known/openid-configuration

This returns either:

404 Not Found or 504 Gateway Timeout

Architecture

Our deployment architecture is:

Angular Application
        |
        | OIDC discovery request
        |
        v
`https://api-product.dev.&lt;company_domain&gt;`
        |
        +-- /Account/Login
        +-- /connect/authorize
        +-- /connect/token
        +-- /.well-known/openid-configuration
        |
        v
AWS API Gateway / VPC Link
        |
        v
AWS ECS
        |
        v
ASP.NET Core / ABP AuthServer
        |
        v
OpenIddict

There is not a separate public AuthServer hostname in this environment.

The following URL is our AuthServer:

https://api-product.dev.<company_domain>/Account/Login

We were previously able to access the AuthServer login page successfully using this URL. Therefore App:SelfUrl is intentionally configured to the same public host: https://api-product.dev.<COMPANY_DOMAIN>. The issue occurs before the user logs in. When the Angular application is opened, the OIDC client automatically requests the discovery endpoint during authentication initialization.

Current configuration

Our AuthServer appsettings.json contains:

{
  "App": {
    "SelfUrl": "https://api-product.dev.<company_domain>",
    "CorsOrigins": "https://*.litmus.financial,https://*.<company_domain>,http://localhost:4200"
  }
}

C# Openiddict configuration

using Microsoft.Extensions.Logging;
using Volo.Abp.OpenIddict;

[DependsOn(typeof(AbpAccountPublicWebOpenIddictModule))]
public class LitmusIdentityServerModule : AbpModule
{
    private static string _configuredIssuer;
    private static bool _isDefaultIssuer;

    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        var configuration = context.Services.GetConfiguration();

        PreConfigure<OpenIddictBuilder>(builder =>
        {
            builder.AddServer(options =>
            {
                options.AllowPasswordFlow();
                options.AllowClientCredentialsFlow();
                options.AllowAuthorizationCodeFlow();
                options.AllowImplicitFlow();
                options.AllowDeviceAuthorizationFlow();
                options.AllowRefreshTokenFlow();
            });
            
            builder.AddValidation(options =>
            {
                options.AddAudiences(/* configured scopes/audiences */);
                options.UseLocalServer();
                options.UseAspNetCore();
            });
        });

        PreConfigure<OpenIddictServerBuilder>(builder =>
        {
            // Issuer configuration
            var selfUrl = configuration["App:SelfUrl"];
           
            // Grant types
            builder.AllowPasswordFlow();
            builder.AllowClientCredentialsFlow();
            builder.AllowAuthorizationCodeFlow();
            builder.AllowImplicitFlow();
            builder.AllowDeviceAuthorizationFlow();
            builder.AllowRefreshTokenFlow();

            // Explicit endpoint configuration
            builder.SetAuthorizationEndpointUris("/connect/authorize");
            builder.SetTokenEndpointUris("/connect/token");
            builder.SetIntrospectionEndpointUris("/connect/introspect");
            builder.SetRevocationEndpointUris("/connect/revocation");
            builder.SetConfigurationEndpointUris("/.well-known/openid-configuration");
            builder.SetUserInfoEndpointUris("/connect/userinfo");
        });

        if (!hostingEnvironment.IsDevelopment())
        {
            PreConfigure<AbpOpenIddictAspNetCoreOptions>(options =>
            {
                options.AddDevelopmentEncryptionAndSigningCertificate = false;
            });

            PreConfigure<OpenIddictServerBuilder>(builder =>
            {
                builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration));
                builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration));
            });
        }
    }

    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        var logger = context.ServiceProvider.GetRequiredService<ILogger<LitmusIdentityServerModule>>();

        if (!string.IsNullOrEmpty(_configuredIssuer))
        {
            if (_isDefaultIssuer)
                logger.LogWarning("OpenIddict issuer: App:SelfUrl not configured. Using default: {Issuer}", _configuredIssuer);
            else
                logger.LogInformation("OpenIddict issuer configured from App:SelfUrl: {Issuer}", _configuredIssuer);
        }
        
        // ... rest of initialization
    }
}
<PackageReference Include="Volo.Abp.Account.Pro.Public.Web.OpenIddict" Version="10.6.0" />
Additional information
  • Deployment is not exposing/using /getEnvConfig
  • We dont have seperate public URL for Auth Server but via https://api-product.dev.<company_domain>/Account/Login

Expectation

We would appreciate a short call with the ABP team so we can share the complete code/configuration and demonstrate the issue live, which should help us identify and resolve the root cause faster.

Note: Our team member has previously raised the following tickets for similar issues encountered during the upgrade.

#10864 #10858

  • Exception message and full stack trace:
  • Steps to reproduce the issue:
Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

31 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    The logs and files you shared last week are what found the jwks_uri, aud and MapInboundClaims problems. Now that those changes are deployed, we need logs from the current build to see what's still failing. What I asked for earlier still stands, nothing has come in yet for the new build:

    • the identitymodel logs from both projects, with the Scheme / X-Forwarded-Proto lines: https://abp.io/support/questions/10870#answer-3a23a2b0-aad3-9c4a-1b74-7106468c25bb
    • the gateway debug log: https://abp.io/support/questions/10870#answer-3a23a193-9b07-d809-4954-dcff5005d4b2

    For the debug log, replace the #if DEBUG / #else / #endif block in the gateway Program.cs with this line, otherwise Release builds drop every Debug entry:

    loggerConfiguration.MinimumLevel.Debug();
    

    In addition, for the same application-configuration call:

    1. The gateway log lines containing [AUTH or Token Validation
    2. Its request headers from the Network tab: is Authorization: Bearer there, and does Cookie contain .AspNetCore.Identity.Application (mask the values)
    3. The current jwks_uri in /.well-known/openid-configuration

    These are the same things I'd need on a call, so sending them first saves that time. Email works too: liming.ma@volosoft.com

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    Thanks, these logs show where it stands now. The scheme change worked: the gateway reads jwks_uri over https and the token signature validates. What's left is the audience, every request to the gateway fails with:

    IDX10206: Unable to validate audience. The 'audiences' parameter is empty.
    

    The access token has no aud claim. ABP fills it from the resources of the granted scopes, read from OpenIddictScopes in the host database, and for your token that lookup comes back empty.

    To confirm that quickly, add this inside AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, ...) in ConfigureAuthenticationSetup, which both the gateway and the identity host use, and redeploy both:

    options.TokenValidationParameters.ValidateAudience = false;
    

    That only hides the problem though, the token is still issued without aud. To find the actual cause, please send the result of this query on the host database:

    select "Name", "Resources" from "OpenIddictScopes";
    

    Along with it, send the code that adds the ua, client_ip and session_id claims to the token, and any override you have of the token endpoint. Those claims are added while the token is being issued, the same step where the resources get set.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    pradeep.pradyumna created

    I have shared details over email. Also I have enabled Debug. Post deployment, I will share the fresh logs

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    Thanks for the logs and the class files, they made this much easier to narrow down.

    The switch didn't take effect because it went into the wrong block. In ConfigureAuthenticationSetup, options.TokenValidationParameters.ValidateAudience = false; sits inside .AddOpenIdConnect(...), while the failing requests are handled by the Bearer scheme, the .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, ...) block right after AddAuthentication.

    Could you move it there, redeploy both the identity service and the gateway, and try a login? If the app comes up, the audience check is the only thing still failing. If it doesn't, the gateway log for that one request would tell us a lot.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    While the audience switch is going in, it's worth deploying one more thing in the same round. These three log points pin down exactly where the resources are lost, so the call can start from an answer instead of a guess.

    Add this to the identity service:

    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.Extensions.Logging;
    using OpenIddict.Abstractions;
    using OpenIddict.Server;
    using Volo.Abp.DependencyInjection;
    using Volo.Abp.OpenIddict;
    
    namespace SCV.Litmus.Identity;
    
    public class AudienceDiagnosticsBefore : IAbpOpenIddictClaimsPrincipalHandler, ITransientDependency
    {
        private readonly ILogger<AudienceDiagnosticsBefore> _logger;
    
        public AudienceDiagnosticsBefore(ILogger<AudienceDiagnosticsBefore> logger)
        {
            _logger = logger;
        }
    
        public Task HandleAsync(AbpOpenIddictClaimsPrincipalHandlerContext context)
        {
            _logger.LogError(
                "AUD-DIAG 1/3 before custom handlers | grant={Grant} requestScope={RequestScope} principalScopes=[{Scopes}] principalResources=[{Resources}]",
                context.OpenIddictRequest.GrantType,
                context.OpenIddictRequest.Scope,
                string.Join(",", context.Principal.GetScopes()),
                string.Join(",", context.Principal.GetResources()));
    
            return Task.CompletedTask;
        }
    }
    
    public class AudienceDiagnosticsAfter : IAbpOpenIddictClaimsPrincipalHandler, ITransientDependency
    {
        private readonly ILogger<AudienceDiagnosticsAfter> _logger;
    
        public AudienceDiagnosticsAfter(ILogger<AudienceDiagnosticsAfter> logger)
        {
            _logger = logger;
        }
    
        public Task HandleAsync(AbpOpenIddictClaimsPrincipalHandlerContext context)
        {
            _logger.LogError(
                "AUD-DIAG 2/3 after custom handlers | principalScopes=[{Scopes}] principalResources=[{Resources}] principalAudiences=[{Audiences}] identityType={IdentityType}",
                string.Join(",", context.Principal.GetScopes()),
                string.Join(",", context.Principal.GetResources()),
                string.Join(",", context.Principal.GetAudiences()),
                context.Principal.Identity?.GetType().Name);
    
            return Task.CompletedTask;
        }
    }
    
    public class AudienceDiagnosticsOnSignIn : IOpenIddictServerHandler<OpenIddictServerEvents.ProcessSignInContext>
    {
        public static OpenIddictServerHandlerDescriptor Descriptor { get; }
            = OpenIddictServerHandlerDescriptor.CreateBuilder<OpenIddictServerEvents.ProcessSignInContext>()
                .AddFilter<OpenIddictServerHandlerFilters.RequireAccessTokenGenerated>()
                .UseSingletonHandler<AudienceDiagnosticsOnSignIn>()
                .SetOrder(OpenIddictServerHandlers.PrepareAccessTokenPrincipal.Descriptor.Order + 1)
                .SetType(OpenIddictServerHandlerType.Custom)
                .Build();
    
        private readonly ILogger<AudienceDiagnosticsOnSignIn> _logger;
    
        public AudienceDiagnosticsOnSignIn(ILogger<AudienceDiagnosticsOnSignIn> logger)
        {
            _logger = logger;
        }
    
        public ValueTask HandleAsync(OpenIddictServerEvents.ProcessSignInContext context)
        {
            _logger.LogError(
                "AUD-DIAG 3/3 access token principal | resources=[{Resources}] audiences=[{Audiences}] scopes=[{Scopes}]",
                string.Join(",", context.AccessTokenPrincipal?.GetResources() ?? Enumerable.Empty<string>()),
                string.Join(",", context.AccessTokenPrincipal?.GetAudiences() ?? Enumerable.Empty<string>()),
                string.Join(",", context.AccessTokenPrincipal?.GetScopes() ?? Enumerable.Empty<string>()));
    
            return default;
        }
    }
    

    Register it in your identity module:

    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        PreConfigure<OpenIddictServerBuilder>(builder =>
        {
            builder.AddEventHandler(AudienceDiagnosticsOnSignIn.Descriptor);
        });
    }
    
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<AbpOpenIddictClaimsPrincipalOptions>(options =>
        {
            options.ClaimsPrincipalHandlers.Insert(0, typeof(AudienceDiagnosticsBefore));
            options.ClaimsPrincipalHandlers.Add(typeof(AudienceDiagnosticsAfter));
        });
    }
    

    Two things to watch when you wire it up. Insert(0, ...) matters, that one has to run before your own handlers. And if your module already has a PreConfigure<OpenIddictServerBuilder> inside an if (!hostingEnvironment.IsDevelopment()) block, don't put the AddEventHandler line in there, it needs to run in every environment.

    Then log in once and send me the three AUD-DIAG lines. On a working setup they come out like this, from a password grant with one API scope:

    AUD-DIAG 1/3 before custom handlers | grant=password requestScope=openid profile ... MyApi principalScopes=[openid,profile,...,MyApi] principalResources=[MyApi]
    AUD-DIAG 2/3 after custom handlers | principalScopes=[openid,profile,...,MyApi] principalResources=[MyApi] principalAudiences=[] identityType=ClaimsIdentity
    AUD-DIAG 3/3 access token principal | resources=[] audiences=[MyApi] scopes=[openid,profile,...,MyApi]
    

    Two empty fields in there are normal, so don't read them as the fault: principalAudiences is empty on 2/3, and resources is empty on 3/3. OpenIddict carries the resources onto the access token principal as audiences, which is the audiences=[MyApi] on 3/3.

    Against that baseline:

    • 1/3 has empty principalResources - the scope lookup against OpenIddictScopes came back with nothing, so it's the data or the database the service is actually connected to
    • 1/3 has resources but 2/3 is empty - one of your own handlers is rebuilding or replacing the principal and dropping them
    • 1/3 and 2/3 both have resources but 3/3 has empty audiences - they're lost between sign-in and token generation

    It logs at Error level so you don't have to change any log configuration, and it comes back out once we're done.

    Two more things that would help before we meet:

    On the database the identity service actually connects to, run this and send the output along with the host and database name:

    select "Name", "Resources", "LastModificationTime" from "OpenIddictScopes" order by "Name";
    

    And clear the browser site data before you judge the login result. A redeploy doesn't touch local storage, so the previous token is still sitting there and it will look like nothing changed.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    Closing this one. A short summary first, since the thread ended up covering several different problems.

    The missing aud came from the scope records. Every row in OpenIddictScopes had IsDeleted = true, and ABP's scope lookup filters soft-deleted rows:

    ... FROM "OpenIddictScopes" AS o
    WHERE NOT (o."IsDeleted") AND (o."Name" = ANY (@names) ...)
    

    Nothing comes back, so there are no resources, so there are no audiences, and OpenIddict only writes aud when the audience set is not empty. Restoring the rows brought aud back:

    update "OpenIddictScopes"
    set "IsDeleted" = false, "DeletionTime" = null, "DeleterId" = null
    where "IsDeleted" = true;
    

    This was hard to see because RegisterScopes(...) registers the scope names in code. The discovery document still listed every scope and the token requests still returned 200. Only the resources are read from the database.

    The 401 on refresh was a separate thing. The refresh call sent client_id but no client_secret, and the client is registered as confidential, so OpenIddict rejected it with invalid_client / ID2198. That rule is not new in ABP 10.6, OpenIddict 5.5 enforces it too and reports it as ID2054.

    Two things worth noting for anyone reading this later. ValidateAudience was turned off during the investigation and should be turned back on once each API host's expected audience is confirmed. And a browser app should use a public client with no secret, ideally with authorization code and PKCE.

    The remaining work is on the application side and the team is handling it. For anything new, please open a new question with the details and logs.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 24, 2026, 12:09
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.