The error IDX10204: Unable to validate issuer. validationParameters.ValidIssuer is null or whitespace AND validationParameters.ValidIssuers is null or empty.
means your API container cannot validate the issuer of the JWT token because the issuer in the token does not match the expected value, or the expected issuer is not configured.
In your docker-compose, you have:
OpenIddict__Validation__Issuer=https://localhost:44317/
OpenIddict__Validation__MetadataAddress=http://visaflowapp-authserver:8080/.well-known/openid-configuration
AuthServer__Authority=https://localhost:44317/
Common Issues and Solutions:
Issuer Mismatch:
OpenIddict__Validation__Issuer
and the issuer
property in the discovery document (/.well-known/openid-configuration
).localhost
inside a container is not the same as localhost
on your host. The API container will try to reach https://localhost:44317/
and fail, because that points to itself, not the AuthServer.Correct Issuer and Metadata Address:
OpenIddict__Validation__Issuer=http://visaflowapp-authserver:8080/
OpenIddict__Validation__MetadataAddress=http://visaflowapp-authserver:8080/.well-known/openid-configuration
App__SelfUrl
and AuthServer__Authority
are also set to http://visaflowapp-authserver:8080
(for internal Docker network).HTTPS/HTTP Consistency:
AuthServer__RequireHttpsMetadata=false
and ensure all URLs (issuer, authority, metadata) use HTTP, not HTTPS, inside Docker.SetIssuer in Code:
PreConfigure<OpenIddictServerBuilder>(builder =>
{
builder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!));
});
Example Docker Compose Internal Configuration:
environment:
- App__SelfUrl=http://visaflowapp-authserver:8080
- AuthServer__Authority=http://visaflowapp-authserver:8080
- AuthServer__RequireHttpsMetadata=false
- OpenIddict__Validation__Issuer=http://visaflowapp-authserver:8080/
- OpenIddict__Validation__MetadataAddress=http://visaflowapp-authserver:8080/.well-known/openid-configuration
Summary of Steps:
RequireHttpsMetadata=false
for internal Docker use.References:
After making these changes, restart your containers. This should resolve the issuer validation error and allow authentication to work in Docker Compose.
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.
To allow your backend to accept requests from a locally running Blazor WebAssembly app while your services are deployed (e.g., on Kubernetes), you need to ensure proper CORS configuration in your microservice solution. In ABP 9.2.3, the CORS configuration approach remains similar, but you should also be aware of the X-Frame-Options
header, which is set by default for security reasons.
Here’s how to configure your environment for local Blazor development with minimal impact:
"App": {
"CorsOrigins": "https://localhost:44346,https://your-staging-domain.com"
}
OnApplicationInitialization
method, ensure:app.UseCors();
This should be before app.UseAuthentication()
and app.UseAuthorization()
.
Refused to display ... in a frame because it set 'X-Frame-Options' to 'SAMEORIGIN'
) is due to the default security headers. If you are embedding the AuthServer in an iframe (e.g., for login), you need to adjust the frame options. In your AuthServer, you can override the default header:app.Use(async (context, next) =>
{
context.Response.Headers.Remove("X-Frame-Options");
// Or set to allow from your localhost
// context.Response.Headers["X-Frame-Options"] = "ALLOW-FROM https://localhost:44346";
await next();
});
Environment-Specific Configuration:
Use environment variables or environment-specific appsettings (e.g., appsettings.Development.json
) to include localhost only in development.
Restart Services: After updating the configuration, restart your backend services to apply the changes.
Browser Cache: Clear your browser cache or use an incognito window to avoid cached CORS or header responses.
This setup allows you to run your Blazor WebAssembly app locally and connect to your remote backend securely and conveniently.
Sources:
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.
To implement time-based permission grants in your ABP application, you need to extend the permission system to include optional StartTime and EndTime for each user-permission grant. Below is a step-by-step guide and code samples for both the backend (C#) and Angular frontend.
Create a new entity (e.g., UserTimeBasedPermission
) to store user-permission grants with optional StartTime and EndTime.
public class UserTimeBasedPermission : Entity<Guid>
{
public Guid UserId { get; set; }
public string PermissionName { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
}
Define a repository and service to manage these grants.
public interface IUserTimeBasedPermissionRepository : IRepository<UserTimeBasedPermission, Guid>
{
Task<UserTimeBasedPermission> FindAsync(Guid userId, string permissionName);
}
public class UserTimeBasedPermissionRepository : EfCoreRepository<YourDbContext, UserTimeBasedPermission, Guid>, IUserTimeBasedPermissionRepository
{
public UserTimeBasedPermissionRepository(IDbContextProvider<YourDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async Task<UserTimeBasedPermission> FindAsync(Guid userId, string permissionName)
{
return await (await GetDbSetAsync())
.FirstOrDefaultAsync(x => x.UserId == userId && x.PermissionName == permissionName);
}
}
Create a custom PermissionValueProvider
to check the time window.
public class TimeBasedPermissionValueProvider : PermissionValueProvider
{
public const string ProviderName = "TimeBased";
private readonly IUserTimeBasedPermissionRepository _repository;
private readonly ICurrentUser _currentUser;
public TimeBasedPermissionValueProvider(
IUserTimeBasedPermissionRepository repository,
ICurrentUser currentUser)
: base(ProviderName)
{
_repository = repository;
_currentUser = currentUser;
}
public override async Task<PermissionValueProviderGrantInfo> CheckAsync(PermissionValueCheckContext context)
{
if (!_currentUser.Id.HasValue)
return PermissionValueProviderGrantInfo.Prohibited();
var grant = await _repository.FindAsync(_currentUser.Id.Value, context.Permission.Name);
if (grant == null)
return PermissionValueProviderGrantInfo.Undefined();
var now = DateTime.UtcNow;
if (grant.StartTime.HasValue && now < grant.StartTime.Value)
return PermissionValueProviderGrantInfo.Prohibited();
if (grant.EndTime.HasValue && now > grant.EndTime.Value)
return PermissionValueProviderGrantInfo.Prohibited();
return PermissionValueProviderGrantInfo.Granted();
}
}
Register your provider in the module:
Configure<AbpPermissionOptions>(options =>
{
options.ValueProviders.Add<TimeBasedPermissionValueProvider>();
});
Create an application service to manage time-based grants:
public class TimeBasedPermissionAppService : ApplicationService
{
private readonly IUserTimeBasedPermissionRepository _repository;
public TimeBasedPermissionAppService(IUserTimeBasedPermissionRepository repository)
{
_repository = repository;
}
public async Task GrantAsync(Guid userId, string permissionName, DateTime? startTime, DateTime? endTime)
{
var grant = await _repository.FindAsync(userId, permissionName);
if (grant == null)
{
grant = new UserTimeBasedPermission
{
Id = Guid.NewGuid(),
UserId = userId,
PermissionName = permissionName,
StartTime = startTime,
EndTime = endTime
};
await _repository.InsertAsync(grant);
}
else
{
grant.StartTime = startTime;
grant.EndTime = endTime;
await _repository.UpdateAsync(grant);
}
}
}
@Injectable({ providedIn: 'root' })
export class TimeBasedPermissionService {
constructor(private http: HttpClient) {}
grant(userId: string, permissionName: string, startTime?: string, endTime?: string): Observable<void> {
return this.http.post<void>('/api/app/time-based-permission/grant', {
userId,
permissionName,
startTime,
endTime
});
}
}
@Component({
selector: 'app-time-based-permission',
template: `
<form (ngSubmit)="grant()">
<input [(ngModel)]="userId" name="userId" placeholder="User ID" required />
<input [(ngModel)]="permissionName" name="permissionName" placeholder="Permission Name" required />
<input [(ngModel)]="startTime" name="startTime" type="datetime-local" placeholder="Start Time" />
<input [(ngModel)]="endTime" name="endTime" type="datetime-local" placeholder="End Time" />
<button type="submit">Grant</button>
</form>
`
})
export class TimeBasedPermissionComponent {
userId = '';
permissionName = '';
startTime?: string;
endTime?: string;
constructor(private service: TimeBasedPermissionService) {}
grant() {
this.service.grant(this.userId, this.permissionName, this.startTime, this.endTime).subscribe(() => {
alert('Permission granted!');
});
}
}
Sources:
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.
To change the built-in ABP API route prefix (such as /api/abp/application-configuration and /api/abp/application-localization), ABP does not provide a global, officially supported way to rename all built-in endpoints’ prefixes (like "abp") out-of-the-box. These endpoints are hardcoded in the ABP modules and are used by the Angular UI and ABP’s internal mechanisms. Overriding them would require forking the relevant ABP modules and updating all usages, which is not recommended as it will break compatibility with future ABP updates and may cause issues with the Angular UI, which expects these endpoints.
To change the generated CSS bundle name (e.g., abp-bundle.css), you can customize the bundle name using ABP’s bundling system. In your module’s ConfigureServices method, configure the AbpBundlingOptions and set your desired bundle name:
Configure<AbpBundlingOptions>(options =>
{
options.StyleBundles.Configure(
StandardBundles.Styles.Global,
bundleConfiguration =>
{
bundleConfiguration.FileName = "company-bundle.css"; // Set your custom bundle name here
}
);
});
Reference:
Summary:
Sources:
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.
This is a known issue with abp-extensible-table in Chromium-based browsers after upgrading to Angular 20 and ABP 9.3.1. The table header misalignment on initial render is caused by how ngx-datatable (used internally by abp-extensible-table) calculates column widths, which can be affected by rendering timing differences in Chromium browsers.
Your workaround (setting canAutoResize = false on ngx-datatable-column) is a valid temporary solution, as it prevents automatic width recalculation that can cause the misalignment.
There is an open issue tracking a similar problem with extensible forms and typeahead, which may be related to rendering/patching timing in Angular 16+ and Chromium:
For a more robust workaround until an official fix is released, you can:
The ABP team is aware of similar rendering issues and is working on improvements for extensible components in upcoming releases. You can follow the progress on the GitHub issue above.
Sources:
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.
Your issue is related to concurrency exceptions in OpenIddict token management, which can cause unexpected logouts or failed refreshes, especially when multiple tabs or clients attempt to refresh tokens simultaneously. This is a known challenge in ABP/OpenIddict-based systems, particularly before the introduction of improved concurrency handling in later ABP versions.
DbUpdateConcurrencyException
occurs when two or more requests try to update or revoke the same refresh token at the same time. OpenIddict uses optimistic concurrency for token updates, so only one request will succeed; others will fail, potentially invalidating the session for the user.Since AbpEfCoreNavigationHelper
and related improvements are not available in v7.3.3, you need to implement workarounds:
A. Prevent Parallel Refresh Token Requests
/connect/token
requests in parallel for the same user/session.Example (pseudo-code for Angular service):
private refreshInProgress = false;
private refreshPromise: Promise<any> | null = null;
refreshToken(): Promise<any> {
if (this.refreshInProgress) {
return this.refreshPromise!;
}
this.refreshInProgress = true;
this.refreshPromise = this.authService.refreshToken().finally(() => {
this.refreshInProgress = false;
this.refreshPromise = null;
});
return this.refreshPromise;
}
B. Handle Concurrency Exceptions Gracefully
C. Reduce Token Lifetime or Use Reference Tokens
By serializing refresh token requests on the client and handling concurrency exceptions gracefully, you can significantly reduce or eliminate these intermittent logout issues in ABP v7.3.3. For a long-term fix, consider upgrading to a newer ABP version with improved OpenIddict concurrency support.
Sources:
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.
To achieve platform-wide (host-level) translations for all tenants in ABP Framework, especially for built-in ABP resources that do not have a Serbian translation, you need to ensure that your dynamic localization implementation always falls back to the host's language texts when a tenant-specific translation is missing.
Your current MyDynamicResourceLocalizer implementation always loads language texts from the host (TenantId = null) by using:
using (currentTenant.Change(null))
{
var texts = scope.ServiceProvider
.GetRequiredService<ILanguageTextRepository>()
.GetList(resource.ResourceName, cultureName);
foreach (var text in texts)
{
cacheItem.Dictionary[text.Name] = text.Value;
}
}
This is correct for host-level translations, but it means that tenant-specific overrides will never be used. However, if your tenants are not seeing all translations, it is likely because:
Key Points & Solution:
Recommended Implementation:
To support both host-wide and tenant-specific translations, update your MyDynamicResourceLocalizer like this:
[Dependency(ReplaceServices = true)]
public class MyDynamicResourceLocalizer : DynamicResourceLocalizer
{
private readonly ICurrentTenant _currentTenant;
public MyDynamicResourceLocalizer(
IServiceScopeFactory serviceScopeFactory,
IDistributedCache<LanguageTextCacheItem> cache,
ICurrentTenant currentTenant)
: base(serviceScopeFactory, cache)
{
_currentTenant = currentTenant;
}
protected override LanguageTextCacheItem CreateCacheItem(LocalizationResourceBase resource, string cultureName)
{
var cacheItem = new LanguageTextCacheItem();
using (var scope = ServiceScopeFactory.CreateScope())
{
var languageTextRepository = scope.ServiceProvider.GetRequiredService<ILanguageTextRepository>();
// 1. Load tenant-specific translations (if any)
var tenantTexts = languageTextRepository.GetList(resource.ResourceName, cultureName);
foreach (var text in tenantTexts)
{
cacheItem.Dictionary[text.Name] = text.Value;
}
// 2. Load host translations for missing keys
using (_currentTenant.Change(null))
{
var hostTexts = languageTextRepository.GetList(resource.ResourceName, cultureName);
foreach (var text in hostTexts)
{
if (!cacheItem.Dictionary.ContainsKey(text.Name))
{
cacheItem.Dictionary[text.Name] = text.Value;
}
}
}
}
return cacheItem;
}
}
For ABP built-in resources in Serbian:
/Localization/AbpUi/sr-Latn.json
).Summary:
Sources:
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.
To add SEO meta tags and ld+json structured data to CMS pages created via the ABP CMS Kit (MVC Core Razor):
Extend the CMS Page Rendering:
/Pages/Cms/Pages/Detail.cshtml
or similar).Add Meta Tags:
<head>
section of your layout or the specific page view, inject meta tags dynamically using the page's properties.@model Volo.CmsKit.Public.Pages.PageDto
@{
Layout = "_Layout";
}
<head>
<title>@Model.Title</title>
<meta name="description" content="@Model.Description" />
<meta property="og:title" content="@Model.Title" />
<meta property="og:description" content="@Model.Description" />
<!-- Add more meta tags as needed -->
</head>
<script type="application/ld+json">
block in the page, using Razor to serialize your data.<script type="application/ld+json">
@{
var jsonLd = new {
"@context" = "https://schema.org",
"@type" = "Article",
"headline" = Model.Title,
"description" = Model.Description,
"datePublished" = Model.CreationTime.ToString("yyyy-MM-dd"),
"author" = new { "@type" = "Person", "name" = Model.CreatorName }
};
var json = System.Text.Json.JsonSerializer.Serialize(jsonLd);
}
@Html.Raw(json)
</script>
Custom Fields (Optional):
Reference:
Sources:
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.
To resolve the error "Could not find the BLOB Storage provider with the type (Volo.Abp.BlobStoring.Database.DatabaseBlobProvider...)", you must ensure that:
dotnet add package Volo.Abp.BlobStoring.Database
*.HttpApi.Host
or main module):[DependsOn(typeof(AbpBlobStoringDatabaseModule))]
public class YourModule : AbpModule
{
// ...
}
ConfigureServices
:Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.ConfigureDefault(container =>
{
container.UseDatabase();
});
// Your other container configurations...
});
AccountProfilePictureContainer
), ensure the default provider is set or that you configure the specific container:options.Containers.Configure<AccountProfilePictureContainer>(container =>
{
container.UseDatabase();
});
If you have multiple providers (e.g., Minio and Database), you must configure each container explicitly to use the correct provider. The default provider is only used for containers that are not explicitly configured.
Restart your application after making these changes.
This will register the Database BLOB provider and resolve the exception.
Sources:
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.
The slow performance of ABP permission cache (PermissionGrants) during the first login is a known behavior, especially when there are a large number of permissions or roles. The method PermissionStore.SetCacheItemsAsync is responsible for loading and caching all granted permissions for the first time, which can be time-consuming if the dataset is large. Subsequent logins are fast because the data is served from the cache.
Key points and recommendations:
For more details and discussion, see the official support thread:
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.