4 Answer(s)
-
0
- Solution
Goal: Use Keycloak (as the OpenID Connect provider) instead of ABP’s built-in Auth Server/OpenIddict in a multi-tenant ABP application.
High-level approach:
- Do not run ABP AuthServer/OpenIddict. Configure your UI and API to use remote authentication against Keycloak via OpenID Connect/OAuth 2.0.
- Resolve tenants from domain/subdomain (or header/query) and ensure the selected tenant is sent during the login flow so authorization happens in the correct tenant.
- Map Keycloak claims to ABP’s expected claims and ensure the user exists in ABP’s identity system (ABP still needs local users/roles/permissions).
Backend (HttpApi.Host or Gateway)
- Disable local auth server and configure JWT validation against Keycloak:
// In YourProjectNameHttpApiHostModule.ConfigureServices public override void ConfigureServices(ServiceConfigurationContext context) { var configuration = context.Services.GetConfiguration(); context.Services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // Keycloak realm OIDC metadata options.Authority = configuration["AuthServer:Authority"]; // e.g. https://keycloak.example.com/realms/myrealm options.RequireHttpsMetadata = true; options.TokenValidationParameters.ValidateAudience = false; // or set ValidAudience(s) if you validate it // Optional: Map Keycloak claim names to ABP’s defaults if needed options.TokenValidationParameters.NameClaimType = ClaimTypes.Name; options.TokenValidationParameters.RoleClaimType = ClaimTypes.Role; }); // Multi-tenancy tenant resolution (domain-based example) Configure<AbpTenantResolveOptions>(opts => { // e.g. {0}.myapp.com → tenant from subdomain opts.AddDomainTenantResolver("{0}.myapp.com"); // Optionally also allow header/query resolvers if you prefer those // opts.AddHeaderTenantResolver(); // opts.AddQueryStringTenantResolver(); }); // CORS if needed for your SPA domains Configure<AbpCorsOptions>(options => { options.AddPolicy(DefaultCorsPolicyName, builder => { builder .WithOrigins( "https://{0}.myapp.com" // replace with exact SPA origins per tenant if not using wildcard CORS service ) .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); } // In your module’s OnApplicationInitialization (or Program.cs in minimal hosting) public override void OnApplicationInitialization(ApplicationInitializationContext context) { var app = context.GetApplicationBuilder(); app.UseCorrelationId(); app.UseRouting(); app.UseCors(); app.UseAuthentication(); app.UseMultiTenancy(); app.UseAuthorization(); app.UseConfiguredEndpoints(); }- Ensure ABP local users exist:
- ABP authorizes against its own identity/permission system. Use an external login flow (OpenID Connect) and provision/link users to ABP users on first login, or implement a synchronization job from Keycloak to ABP Identity.
- If you use MVC/Razor pages: Add an OpenID Connect external login handler that uses Keycloak as the external provider and creates/links local ABP users upon first sign-in.
- Frontend (Angular/Blazor/MVC)
- Angular:
- Configure oAuthConfig to use Keycloak realm as issuer and app’s clientId:
- Angular:
oAuthConfig: { issuer: 'https://keycloak.example.com/realms/myrealm', redirectUri: 'https://{0}.myapp.com', // domain-based tenant clientId: 'my-angular-client', responseType: 'code', scope: 'openid profile email offline_access', requireHttps: true }- Use domain-based tenant resolution on the UI by setting baseUrl/Authority with {0} placeholder and configure AbpTenantResolveOptions on the backend to match. This prevents tenant switching and ensures the __tenant value aligns with the subdomain. - When navigating to login, optionally pass __tenant if you rely on header/query resolvers:import { AuthService, SessionStateService } from '@abp/ng.core'; // ... const tenantId = this.sessionState.getTenant()?.id; this.authService.navigateToLogin(tenantId ? { __tenant: `${tenantId}` } : undefined);Blazor WebAssembly:
- Set AuthServer:Authority and RemoteServices:Default in appsettings to Keycloak realm and your API base. If you use subdomain tenancy, place {0} and replace it at startup from the current host before configuring OIDC and remote services.
Keycloak configuration
- Create a realm (or one realm per tenant if you intend tenant-per-realm). Most ABP setups use one realm and resolve tenants by domain; your ABP-side tenant resolver remains the source of truth.
- Create a confidential/public client for your SPA/API flows:
- Allowed redirect URIs: add each tenant app URL, e.g. https://tenant1.myapp.com, https://tenant2.myapp.com (avoid wildcards for redirect URIs; list exact URLs).
- Web origins/CORS: add SPA origins accordingly.
- Expose roles/claims:
- Map realm or client roles to the role claim Keycloak emits (ABP expects role in the standard role claim). If the claim types differ, set RoleClaimType/NameClaimType in TokenValidationParameters accordingly on the backend.
Multi-tenancy specifics
- Prefer domain/subdomain tenant resolver on backend to prevent switching. Example:
Configure<AbpTenantResolveOptions>(options => { options.AddDomainTenantResolver("{0}.myapp.com"); });Ensure headers with underscores (__tenant) are forwarded if you use header/query resolvers behind proxies like NGINX (set underscores_in_headers on). With pure domain resolver, this is not needed.
Place UseAuthentication() before UseMultiTenancy() in the middleware order so tenant can be resolved from token/headers correctly where applicable.
Common pitfalls
- Mismatch between Angular issuer and backend Authority: issuer must point to the Keycloak realm URL. Backend JWT validation Authority must match too.
- Missing redirect URIs per tenant domain in Keycloak client → causes login loops.
- UI or gateway blocks the __tenant header (if you rely on it) → tenant falls back to Host.
- Not provisioning ABP users → tokens validate but ABP authorization fails because local user/permissions are missing.
- Sources
- Domain-based tenant resolution and wildcard/tenant formats (community articles validated):
- https://abp.io/community/articles/v9y8da7v
- https://abp.io/community/articles/c1x4un8x
- Passing __tenant during Angular login (support thread):
- https://abp.io/support/questions/9661
- Multi-tenancy middleware order (support thread):
- https://abp.io/support/questions/9577
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
How can I change the flow for creation user and tenant?
-
0
- Solution
Goal: Customize the creation flow for Tenants (SaaS module) and Users (Identity module) in ABP (e.g., validate additional inputs, create related domain data, call external systems, or change defaults).
Change the Tenant creation flow (SaaS)
- Option A: Override the application service methods
- Create a derived app service to intercept Create/Update operations of tenants and register it to replace the default service:
- Option A: Override the application service methods
using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.DependencyInjection; using Volo.Saas.Tenants; using Volo.Abp.Application.Services; [Dependency(ReplaceServices = true)] [ExposeServices(typeof(ITenantAppService))] public class MyTenantAppService : TenantAppService, ITenantAppService { public MyTenantAppService(ITenantRepository tenantRepository, ITenantManager tenantManager) : base(tenantRepository, tenantManager) { } public override async Task<TenantDto> CreateAsync(TenantCreateDto input) { // 1) Custom validation if (string.IsNullOrWhiteSpace(input.Name)) throw new BusinessException("TenantNameRequired"); // 2) Pre-processing (e.g., reserve subdomain, call payment API) // await _myBillingClient.CreateCustomerAsync(input.Name, ...); var result = await base.CreateAsync(input); // 3) Post-processing (e.g., seed additional data for the new tenant) await SeedForTenantAsync(result.Id); return result; } public override async Task<TenantDto> UpdateAsync(Guid id, TenantUpdateDto input) { // Custom logic before update var updated = await base.UpdateAsync(id, input); // Custom logic after update return updated; } private async Task SeedForTenantAsync(Guid tenantId) { using (CurrentTenant.Change(tenantId)) { // Create default domain aggregates, settings, features, etc. // await _myAppService.InitializeDefaultsAsync(); } } }- Option B: Handle tenant domain events
- Subscribe to tenant lifecycle events and run your logic without replacing services:
using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus; using Volo.Saas.Tenants; public class TenantCreatedHandler : ILocalEventHandler<TenantCreatedEto>, ITransientDependency { public async Task HandleEventAsync(TenantCreatedEto eventData) { using (CurrentTenant.Change(eventData.Id)) { // Seed default data, features, etc. } } }Change database provisioning behavior
- If you need to control connection string creation or custom provisioning, intercept after CreateAsync when you have TenantId and set connection strings (Repository/Manager) then run migrations (or queue them) and seed data in the tenant scope.
Change the User creation flow (Identity)
- Option A: Replace the UserAppService
using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; [Dependency(ReplaceServices = true)] [ExposeServices(typeof(IIdentityUserAppService))] public class MyIdentityUserAppService : IdentityUserAppService, IIdentityUserAppService { public MyIdentityUserAppService(IdentityUserManager userManager, IIdentityUserRepository userRepository, IIdentityRoleRepository roleRepository) : base(userManager, userRepository, roleRepository) { } public override async Task<IdentityUserDto> CreateAsync(IdentityUserCreateDto input) { // Custom validation (e.g., enforce password policy, email domain checks) // if (!input.Email.EndsWith("@mycorp.com")) throw new BusinessException("InvalidEmailDomain"); var user = await base.CreateAsync(input); // Post actions: assign roles/permissions, create related domain data // await UserManager.AddToRoleAsync(await UserManager.GetByIdAsync(user.Id), "MyDefaultRole"); return user; } }- Option B: Use Identity domain events or override the IdentityUserManager
- Subscribe to user creation events to avoid replacing the app service:
using Volo.Abp.EventBus; using Volo.Abp.Identity; public class UserCreatedHandler : ILocalEventHandler<IdentityUserCreatedEto>, ITransientDependency { public async Task HandleEventAsync(IdentityUserCreatedEto eventData) { using (CurrentTenant.Change(eventData.TenantId)) { // E.g., create a profile aggregate, send welcome mail, assign features based on tenant plan } } }- Alternatively, derive from IdentityUserManager and override CreateAsync to enforce invariants at the domain layer, then register your manager with ReplaceServices.Validation, permissions, and DTO mapping
- Apply FluentValidation/ABP validation attributes on your custom input DTOs and let ABP’s validation pipeline run.
- If you introduce new fields on create, extend the DTOs (using object extension system) so they flow through UI/API, then map in your overridden service.
- Ensure your custom service keeps ABP permission checks (e.g., IdentityPermissions.Users.Create, SaasPermissions.Tenants.Create) or customize as needed.
Multi-tenancy considerations
- Wrap all tenant-specific post-create logic in using (CurrentTenant.Change(tenantId)).
- For host-triggered user creation inside a tenant, ensure CurrentTenant is correctly set before calling user APIs.
- If you remove tenant switch and infer tenant by email/username, resolve tenant early and set CurrentTenant before calling Identity (see examples in the referenced guides).
- Sources
- Multi-Tenancy overview (tenant resolution, scopes):
- https://abp.io/docs/latest/framework/architecture/multi-tenancy
- Angular multi-tenancy behavior (tenant header flow):
- https://abp.io/docs/latest/framework/ui/angular/multi-tenancy
- Community examples for changing tenant selection and simplifying login (pattern for setting CurrentTenant and overriding UI/service flows):
- https://abp.io/community/articles/4foaup7p
- https://abp.io/support/questions/9283
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
No I need to integrate flow to create user/tenant with keycloak