Open Closed

Replace Auth with keycloak #10246


User avatar
0
andmattia created

How can we replace auth server with Keycloak in a multitenant env?


4 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    1. 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)

      1. 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();
      }
    
    1. 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:
    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.
    1. 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.

  • User Avatar
    0
    andmattia created

    How can I change the flow for creation user and tenant?

  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    1. 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:
    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).
    1. 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.

  • User Avatar
    0
    andmattia created

    No I need to integrate flow to create user/tenant with keycloak

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.1.0-preview. Updated on December 17, 2025, 07:08
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.