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.<company_domain>`
|
+-- /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.
- Exception message and full stack trace:
- Steps to reproduce the issue:
31 Answer(s)
-
0
Hi,
The logs and files you shared last week are what found the
jwks_uri,audandMapInboundClaimsproblems. 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
identitymodellogs from both projects, with theScheme/X-Forwarded-Protolines: 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/#endifblock in the gatewayProgram.cswith this line, otherwise Release builds drop everyDebugentry:loggerConfiguration.MinimumLevel.Debug();In addition, for the same
application-configurationcall:- The gateway log lines containing
[AUTHorToken Validation - Its request headers from the Network tab: is
Authorization: Bearerthere, and doesCookiecontain.AspNetCore.Identity.Application(mask the values) - The current
jwks_uriin/.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) - the
-
0
Hi,
Thanks, these logs show where it stands now. The scheme change worked: the gateway reads
jwks_uriover 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
audclaim. ABP fills it from the resources of the granted scopes, read fromOpenIddictScopesin the host database, and for your token that lookup comes back empty.To confirm that quickly, add this inside
AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, ...)inConfigureAuthenticationSetup, 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_ipandsession_idclaims 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) -
0
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) -
0
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 theBearerscheme, the.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, ...)block right afterAddAuthentication.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) -
0
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 aPreConfigure<OpenIddictServerBuilder>inside anif (!hostingEnvironment.IsDevelopment())block, don't put theAddEventHandlerline in there, it needs to run in every environment.Then log in once and send me the three
AUD-DIAGlines. 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:
principalAudiencesis empty on2/3, andresourcesis empty on3/3. OpenIddict carries the resources onto the access token principal as audiences, which is theaudiences=[MyApi]on3/3.Against that baseline:
1/3has emptyprincipalResources- the scope lookup againstOpenIddictScopescame back with nothing, so it's the data or the database the service is actually connected to1/3has resources but2/3is empty - one of your own handlers is rebuilding or replacing the principal and dropping them1/3and2/3both have resources but3/3has emptyaudiences- 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) -
0
Hi,
Closing this one. A short summary first, since the thread ended up covering several different problems.
The missing
audcame from the scope records. Every row inOpenIddictScopeshadIsDeleted = 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
audwhen the audience set is not empty. Restoring the rows broughtaudback: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_idbut noclient_secret, and the client is registered as confidential, so OpenIddict rejected it withinvalid_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.
ValidateAudiencewas 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)