Open Closed

How to configure tenant resolver in order to determine current tenant by the whole domain? #5650


User avatar
0
datdv1 created

Hi ABP Support Team! We a using abp commercial

UI framework: angular

ABP Version: 7.3.2

Data access: MongoDB

Deployment: Azure Kubenetes Service

Template type: Application template, separate Authen Project

Currently, I'm Following and configured in angular application with document: https://docs.abp.io/en/abp/latest/UI/Angular/Multi-Tenancy

When access to host tenant, it is working, however when access on tenant, it is not working. It cannot resolver issuer domain. Can you help me for this? I think because the Authen project has set config issuer domain, is that the problem?

Here is the configmap authen:

Here is the configmap angular:

Here is the configmap Host project:

Here is the configmap Authproject:


56 Answer(s)
  • User Avatar
    0
    datdv1 created

    Hi maliming! How to contact to angular team? Can you support for me contact to angular team?

  • User Avatar
    0
    mahmut.gundogdu created

    I have created an app with angular and openiddict in 7.3.2 version in my local. I defined local dns in my host file for testing. 127.0.0.1 mahmut.local 127.0.0.1 odin.mahmut.local

    I created a tenant that name is odin. It works on my local. I think there is a step I skipped before. So I am updating documentation and I will crate an article too.

  • User Avatar
    0
    datdv1 created

    Hi mahmut.gundogdu! Can you guide for me step by step? Because, I need this part urgently!

  • User Avatar
    0
    datdv1 created

    I appreciate it

  • User Avatar
    0
    mahmut.gundogdu created

    I appreciate it

    Sure. now, I am writing step by step a article. When I finished, I will sent here.

  • User Avatar
    0
    mahmut.gundogdu created

    Here a video for working solution . https://www.veed.io/view/0676526b-08cb-41cd-964c-a3b7b7f44f16?panel=share

  • User Avatar
    0
    datdv1 created

    Thank you for mahmut.gundogdu!

  • User Avatar
    0
    datdv1 created

    Hi mahmut.gundogdu! Can you screen shot all configure that work and share to me?

  • User Avatar
    0
    mahmut.gundogdu created

    I couldn't share the article yet but I have finished. I have added shoots and even source codes. for a now you can read the link

    https://docs.google.com/document/d/1VWUaGTJmT8Fc9uLlT8ohlfaB1hgKPYvzz-WURp65i90/edit?usp=sharing

    I think your tenant resolver code is missing. Because I forgot the add these code in configure section. I can login the host but I couldn't login tenant. I would check the section.

    Configure<AbpTenantResolveOptions>(options => { options.AddDomainTenantResolver("{0}.multitenancydemo.local"); });

  • User Avatar
    0
    datdv1 created

    Hi mahmut.gundogdu! Because, I need this part urgently for tomorrow! I see that you have finished the document Could you join a google meeting with me? I think it will be easier for us to discuess with our situation

  • User Avatar
    0
    datdv1 created

    Hi mahmut.gundogdu! When you finished the document, Please sent this for me.

  • User Avatar
    0
    mahmut.gundogdu created

    Hi mahmut.gundogdu! When you finished the document, Please sent this for me.

    Here the article and example. https://github.com/mahmut-gundogdu/ABP-Domain-based-Tenant-Example/blob/main/article/index.md

  • User Avatar
    0
    datdv1 created

    Hi mahmut.gundogdu! I have taken a look at your code I think that my configuration is like your configuration Could you join us a meeting via google meet or zoom? In the online meeting, you can view my code and fix my issue directly

  • User Avatar
    0
    datdv1 created

    If you cannot arrange time to join our meeting I will send you my code, you can view the issue and fix the issue After you finish fixing, I will deploy code and we can check it together

  • User Avatar
    0
    datdv1 created

    Hello, I have compared between my configuration and your configuration in your git hub link . My configuration is same to your configuration. When my project deployed to Azure Kubernetes Service, the isssue still existed. I cannot login in my tenant angular. As you can se in the below image API token can be called successfully and return access_token, but angular cannot redirect to home page

    I think we can disscuss in a online meeting like goole meeting or zoom meeting, because you can seee my project more detailed If you cannot arrange the time for a online meeting, I will send you my source code, you can take a look and help me to fix the wrong configuration. So that I can deploy to Azure Kubrentes Service and report you the reulst.

  • User Avatar
    0
    datdv1 created

    Hi mahmut.gundogdu Can you support me for this?

  • User Avatar
    0
    mahmut.gundogdu created

    Hello, I have compared between my configuration and your configuration in your git hub link . My configuration is same to your configuration. When my project deployed to Azure Kubernetes Service, the isssue still existed. I cannot login in my tenant angular. As you can se in the below image API token can be called successfully and return access_token, but angular cannot redirect to home page

    I think we can disscuss in a online meeting like goole meeting or zoom meeting, because you can seee my project more detailed If you cannot arrange the time for a online meeting, I will send you my source code, you can take a look and help me to fix the wrong configuration. So that I can deploy to Azure Kubrentes Service and report you the reulst.

    To better assist you, we're arranging a special 1:1 online meeting – a deviation from our usual support process. This dedicated session will allow us to address your concerns effectively. Kindly share your preferred time, and we'll ensure a seamless meeting experience. Feel free to contact me at mahmut.gundogdu@volosoft.com to coordinate the meeting details.

  • User Avatar
    0
    datdv1 created

    Hi mahmut.gundogdu Thank you very much. I have sent you a specific appointment via email: mahmut.gundogdu@volosoft.com

  • User Avatar
    0
    datdv1 created

    Hi mahmut.gundogdu! After finishing out a meeting. We configured with you suggestion.

    added configure to Host project:

    added configure to Authen project

    I got problems: 1, Host project could not resolve tenant with domain. bellow is a result:

    2, in angular side of tenant still happens issue login successfully But cannot redirect to home page.

    I'm looking forward to you response. Because, My work is very urgent.

  • User Avatar
    0
    datdv1 created

    Hi mahmut! Have a good day. Have you tested with the tiered application template yet? I'm looking forward to your response. Because, My work is very urgent.

  • User Avatar
    0
    mahmut.gundogdu created

    yes I have tested with Seperated auth server options and there is an issue. I couldn't login with tenant. I have opened an issue and I am working with teammate that expert in backend. By the way sorry i have made mistaked. The app template name is not called as "tiered". That option is called --separate-auth-server. We are working on the issue.

  • User Avatar
    0
    datdv1 created

    Hi mahmut.gundogdu! Thank you very much. I hope receive a solution.

  • User Avatar
    0
    datdv1 created

    Hi mahmut! Beside of that, I realized that the code only resolve tenant with a fixed suffix domain. For example: testing-app.{0}.com Could you please guild me how to configure resolve tenant with whole domain? For example: testing-app.{0}

  • User Avatar
    0
    datdv1 created

    Hi mahmut! We have different tenants with different domains. Do their domains have to share the same suffix with the host’s domain? It could be much better if you can resolve tenant by the whole domain (including suffix).

  • User Avatar
    0
    LinchArnold created

    Hi, I came across this problem months ago.

    After some research, I fixed it with the following steps:

    In AuthServer Module.ts :

    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        // other codes.
    
        PreConfigure<AbpOpenIddictAspNetCoreOptions>(options =>
                {
                    options.AddDevelopmentEncryptionAndSigningCertificate = false;
                });
    
                PreConfigure<OpenIddictServerBuilder>(builder =>
                {
                    builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration));
                    builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration));
                    if (!hostingEnvironment.IsProduction())
                    {
                        builder.SetIssuer(new Uri(configuration["AuthServer:Authority"]!));
                    }
                });
                
                    PreConfigure<AbpOpenIddictWildcardDomainOptions>(options =>
                    {
                        options.EnableWildcardDomainSupport = true;
                        options.WildcardDomainsFormat.Add(configuration["App:AngularWildcardUrl"]);
                    });
    
                    PreConfigure<OpenIddictServerOptions>(options =>
                    {
                        options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator;
                        options.TokenValidationParameters.ValidIssuers = new[]
                        {
                            configuration["AuthServer:Authority"].EnsureEndsWith('/'),
                            configuration["AuthServer:WildcardAuthority"].EnsureEndsWith('/')
                        };
                    });
          
    }
    

    Note: TokenWildcardIssuerValidator.IssuerValidator is from package <PackageReference Include="Owl.TokenWildcardIssuerValidator" Version="1.0.0" />

    In HttpApi.Host module ts:

    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        // other codes
        context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.Authority = configuration["AuthServer:Authority"];
                    options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
                    options.Audience = "PayloadLimitPfmNext";
    
                    if (hostingEnvironment.IsProduction())
                    {
                        options.TokenValidationParameters.IssuerValidator = TokenWildcardIssuerValidator.IssuerValidator;
                        options.TokenValidationParameters.ValidIssuers = new[]
                        {
                            configuration["AuthServer:Authority"].EnsureEndsWith('/'),
                            configuration["AuthServer:WildcardAuthority"].EnsureEndsWith('/')
                        };
                    }
                });
    }
    

    Note: TokenWildcardIssuerValidator.IssuerValidator is from package <PackageReference Include="Owl.TokenWildcardIssuerValidator" Version="1.0.0" />

    In Angular:

    1. Create a multi-tenancy-utils.ts file, this will be override the default implementation of ABP framework:
    import {
      ABP,
      CORE_OPTIONS,
      Environment,
      EnvironmentService,
      createTokenParser,
      getRemoteEnv,
    } from '@abp/ng.core';
    import { Injector } from '@angular/core';
    import clone from 'just-clone';
    
    const tenancyPlaceholder = '{0}';
    
    function getCurrentTenancyName(appBaseUrl: string): string {
      if (appBaseUrl.charAt(appBaseUrl.length - 1) !== '/') appBaseUrl += '/';
      const parseTokens = createTokenParser(appBaseUrl);
      const token = tenancyPlaceholder.replace(/[}{]/g, '');
      return parseTokens(window.location.href)[token]?.[0];
    }
    
    export function cleanPlaceholderFromHostUrl(injector: Injector) {
      const fn = async () => {
        const environmentService = injector.get(EnvironmentService);
        const options = injector.get(CORE_OPTIONS) as ABP.Root;
        environmentService.setState(options.environment as Environment);
        await getRemoteEnv(injector, options.environment);
        const baseUrl =
          environmentService.getEnvironment()['appBaseUrl'] ||
          environmentService.getEnvironment().application?.baseUrl ||
          '';
        const tenancyName = getCurrentTenancyName(baseUrl);
    
        if (!tenancyName) {
          /**
           * If there is no tenant, we still have to clean up {0}. from baseUrl to avoid incorrect http requests.
           */
          replaceTenantNameWithinEnvironment(injector, '', tenancyPlaceholder + '.');
          replaceTenantNameWithinEnvironment(injector, '', tenancyPlaceholder);
        }
    
        return Promise.resolve();
      };
    
      return fn;
    }
    
    function replaceTenantNameWithinEnvironment(
      injector: Injector,
      tenancyName: string,
      placeholder = tenancyPlaceholder
    ) {
      const environmentService = injector.get(EnvironmentService);
    
      const environment = clone(environmentService.getEnvironment()) as Environment;
    
      if (environment.application.baseUrl) {
        environment.application.baseUrl = environment.application.baseUrl.replace(
          placeholder,
          tenancyName
        );
      }
    
      if (environment.oAuthConfig?.redirectUri) {
        environment.oAuthConfig.redirectUri = environment.oAuthConfig.redirectUri.replace(
          placeholder,
          tenancyName
        );
      }
    
      if (!environment.oAuthConfig) {
        environment.oAuthConfig = {};
      }
      environment.oAuthConfig.issuer = (environment.oAuthConfig.issuer || '').replace(
        placeholder,
        tenancyName
      );
    
      environment.oAuthConfig.clientId = (environment.oAuthConfig.clientId || '').replace(
        placeholder,
        tenancyName
      );
    
      Object.keys(environment.apis).forEach(api => {
        Object.keys(environment.apis[api]).forEach(key => {
          environment.apis[api][key] = (environment.apis[api][key] || '').replace(
            placeholder,
            tenancyName
          );
        });
      });
    
      return environmentService.setState(environment);
    }
    
    1. Create domain-resolver.module.ts file:
    import { CommonModule } from '@angular/common';
    import { HttpClientModule } from '@angular/common/http';
    import { APP_INITIALIZER, Injector, NgModule } from '@angular/core';
    import { cleanPlaceholderFromHostUrl } from './multi-tenancy-utils';
    
    @NgModule({
      imports: [CommonModule, HttpClientModule],
      providers: [
        {
          provide: APP_INITIALIZER,
          multi: true,
          deps: [Injector],
          useFactory: cleanPlaceholderFromHostUrl,
        },
      ],
    })
    export class DomainResolverModule {}
    
    1. Import the DomainResolverModule in your AppModule:
    import { CoreModule } from '@abp/ng.core';
    import { GdprConfigModule } from '@volo/abp.ng.gdpr/config';
    import { SettingManagementConfigModule } from '@abp/ng.setting-management/config';
    import { HTTP_ERROR_HANDLER, ThemeSharedModule } from '@abp/ng.theme.shared';
    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
    import { CommercialUiConfigModule } from '@volo/abp.commercial.ng.ui/config';
    import { AccountAdminConfigModule } from '@volo/abp.ng.account/admin/config';
    import { AccountPublicConfigModule } from '@volo/abp.ng.account/public/config';
    import { AuditLoggingConfigModule } from '@volo/abp.ng.audit-logging/config';
    import { IdentityConfigModule } from '@volo/abp.ng.identity/config';
    import { LanguageManagementConfigModule } from '@volo/abp.ng.language-management/config';
    import { registerLocale } from '@volo/abp.ng.language-management/locale';
    import { SaasConfigModule } from '@volo/abp.ng.saas/config';
    import { TextTemplateManagementConfigModule } from '@volo/abp.ng.text-template-management/config';
    import { HttpErrorComponent, ThemeLeptonModule } from '@volo/abp.ng.theme.lepton';
    import { environment } from '../environments/environment';
    import { AppRoutingModule } from './app-routing.module';
    import { AppComponent } from './app.component';
    import { APP_ROUTE_PROVIDER } from './route.provider';
    import { OpeniddictproConfigModule } from '@volo/abp.ng.openiddictpro/config';
    import { FeatureManagementModule } from '@abp/ng.feature-management';
    import { AbpOAuthModule } from '@abp/ng.oauth';
    import { ServiceWorkerModule } from '@angular/service-worker';
    import { APP_VALIDATION_PROVIDER } from './validation/app.validation.provider';
    import { DomainResolverModule } from './customization';
    
    @NgModule({
      declarations: [AppComponent],
      imports: [
        BrowserModule,
        BrowserAnimationsModule,
        AppRoutingModule,
        DomainResolverModule,
        CoreModule.forRoot({
          environment,
          registerLocaleFn: registerLocale(),
        }),
        AbpOAuthModule.forRoot(),
        ThemeSharedModule.forRoot({
          httpErrorConfig: {
            errorScreen: {
              component: HttpErrorComponent,
              forWhichErrors: [401, 403, 404, 500],
              hideCloseIcon: true,
            },
          },
        }),
        // Other import statements
      ],
      providers: [
        APP_ROUTE_PROVIDER,
        APP_VALIDATION_PROVIDER
      ],
      bootstrap: [AppComponent],
    })
    export class AppModule {}
    

    After this, if the website wildcard is web{0}.sample.com, the web.sample.com will be host, webtenant1.sample.com will be tenant tenant1.

Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11