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 angular:
56 Answer(s)
-
0
Hi maliming! How to contact to angular team? Can you support for me contact to angular team?
-
0
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.
-
0
Hi mahmut.gundogdu! Can you guide for me step by step? Because, I need this part urgently!
-
0
I appreciate it
-
0
I appreciate it
Sure. now, I am writing step by step a article. When I finished, I will sent here.
-
0
Here a video for working solution . https://www.veed.io/view/0676526b-08cb-41cd-964c-a3b7b7f44f16?panel=share
-
0
Thank you for mahmut.gundogdu!
-
0
Hi mahmut.gundogdu! Can you screen shot all configure that work and share to me?
-
0
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"); });
-
0
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
-
0
Hi mahmut.gundogdu! When you finished the document, Please sent this for me.
-
0
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
-
0
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
-
0
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
-
0
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.
-
0
Hi mahmut.gundogdu Can you support me for this?
-
0
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.
-
0
Hi mahmut.gundogdu Thank you very much. I have sent you a specific appointment via email: mahmut.gundogdu@volosoft.com
-
0
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.
-
0
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.
-
0
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. -
0
Hi mahmut.gundogdu! Thank you very much. I hope receive a solution.
-
0
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}
-
0
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).
-
0
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:
- 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); }
- 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 {}
- Import the
DomainResolverModule
in yourAppModule
:
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
, theweb.sample.com
will be host,webtenant1.sample.com
will be tenanttenant1
. - Create a