Open Closed

Domain/Subdomain Tenant Resolver configuration question #8282


User avatar
0
jacek.bialy created
  • ABP Framework version: v8.3.0
  • UI Type: Blazor Server
  • Database System: EF Core (SQL Server)
  • Tiered (for MVC) or Auth Server Separated (for Angular): yes
  • Exception message and full stack trace:
  • Steps to reproduce the issue:

I have a question regarding domain/subdomain tenant resolving. Docs (https://abp.io/docs/latest/framework/architecture/multi-tenancy#domain-subdomain-tenant-resolver) say that we have to configure portal, api and auth subdomains for each tenant which means that we will use 3 subdomains for every tenant. Is it possible to use less subdomains to achive this kind of resolving? For example we might want to have main portal with subdomains like {tenantName}.portal.com but both API and AuthServer would have only one domain configured like api.com and auth.com. I'm asking about that because on Azure there is a limit of 250 subdomains, so we'd like to use as less domains as it could be for one tenant. If it's somehow possible please feel free to share with some configuration/code examples.


11 Answer(s)
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    For example we might want to have main portal with subdomains like {tenantName}.portal.com but both API and AuthServer would have only one domain configured like api.com and auth.com.

    Yes, it's possible, you can try it a try.

  • User Avatar
    0
    jacek.bialy created

    I've already tried and I don't know how to properly redirect user from portal to AuthServer. I tried to use __tenant parameter, so in PortalBlazorModule.cs i've added:

    options.Events.OnRedirectToIdentityProviderForSignOut = redirectContext =>
    {
         var currentTenant = redirectContext.HttpContext.RequestServices.GetRequiredService<ICurrentTenant>();
         redirectContext.ProtocolMessage.IssuerAddress += $"?__tenant={currentTenant?.Id}";
         return Task.CompletedTask;
    };
    

    But: - It does not hide Tenant field on login page (only enabling domain tenant resolver hides that field). Tenant is correctly selected but field is still visible. - It does not work with host. __tenant is sent as empty value and then previously selected tenant is selected not the empty tenant/value - host.

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    It does not work with host. __tenant is sent as empty value and then previously selected tenant is selected not the empty tenant/value - host.

    it works for me.

    It does not hide Tenant field on login page (only enabling domain tenant resolver hides that field). Tenant is correctly selected but field is still visible.

    You can override the account layout page.

    For example:

    @using Microsoft.Extensions.Localization
    @using Microsoft.Extensions.Options
    @using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX
    @using Volo.Abp.LeptonX.Shared.Localization;
    @using Volo.Abp.Localization
    @using Volo.Abp.AspNetCore.Mvc.UI.Components.LayoutHook
    @using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX.Bundling
    @using Volo.Abp.AspNetCore.Mvc.UI.Theming
    @using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetScripts
    @using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetStyles
    @using Volo.Abp.Ui.Branding
    @using Volo.Abp.AspNetCore.Mvc.AntiForgery
    @using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.Localization
    @using Volo.Abp.AspNetCore.MultiTenancy
    @using Volo.Abp.MultiTenancy
    @using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX.Themes.LeptonX.Components.Common.PageAlerts
    @using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX.Themes.LeptonX.Components.SideMenu.Toolbar.LanguageSwitch
    @using Microsoft.AspNetCore.Http.Extensions
    @using Volo.Abp.Ui.LayoutHooks
    @inject IAbpAntiForgeryManager AbpAntiForgeryManager
    @inject IBrandingProvider BrandingProvider
    @inject LeptonXStyleProvider LeptonXStyleProvider
    @inject IStringLocalizer<AbpUiMultiTenancyResource> MultiTenancyStringLocalizer
    @inject IStringLocalizer<LeptonXResource> L
    @inject ITenantResolveResultAccessor TenantResolveResultAccessor
    @inject IOptions<AbpMultiTenancyOptions> MultiTenancyOptions
    @inject ICurrentTenant CurrentTenant
    @inject ThemeLanguageInfoProvider ThemeLanguageInfoProvider
    @inject Volo.Abp.AspNetCore.Mvc.UI.Layout.IPageLayout PageLayout
    
    @{
        AbpAntiForgeryManager.SetCookie();
        var langDir = CultureHelper.IsRtl ? "rtl" : string.Empty;
        var title = $"{ViewBag.Title ?? PageLayout.Content.Title} | {BrandingProvider.AppName}".Trim('|', ' ');
    
    
        var languageInfo = await ThemeLanguageInfoProvider.GetLanguageSwitchViewComponentModel();
        var returnUrl = System.Net.WebUtility.UrlEncode(Context.Request.GetEncodedPathAndQuery());
    
        var logoUrl = BrandingProvider.LogoUrl == null ? null : "--lpx-logo: url(" + BrandingProvider.LogoUrl + ");";
        var logoReverseUrl = BrandingProvider.LogoReverseUrl == null ? null : "--lpx-logo: url(" + BrandingProvider.LogoReverseUrl + ");";
        var selectedStyle = await LeptonXStyleProvider.GetSelectedStyleAsync();
    
        var selectedStyleFileName = CultureHelper.IsRtl ? selectedStyle + ".rtl" : selectedStyle;
    }
    <!DOCTYPE html>
    <html lang="@CultureInfo.CurrentCulture.Name" dir="@langDir">
    
    <head>
    
        @await Component.InvokeLayoutHookAsync(LayoutHooks.Head.First, StandardLayouts.Account)
    
        <title>@title</title>
    
        <meta name="viewport" content="width=device-width,initial-scale=1.0" />
        <meta charset="UTF-8" />
        <meta name="description" content="@ViewBag.MetaDescription">
    
        <link rel="icon" href="~/favicon.svg" type="image/svg+xml">
    
        <abp-style-bundle name="@LeptonXThemeBundles.Styles.Global" />
    
        <link href="~/Themes/LeptonX/Global/side-menu/css/bootstrap-@(selectedStyleFileName).css" type="text/css"
              rel="stylesheet" id="lpx-theme-bootstrap-@selectedStyle" />
        <link href="~/Themes/LeptonX/Global/side-menu/css/@(selectedStyleFileName).css" type="text/css" rel="stylesheet"
              id="lpx-theme-color-@selectedStyle" />
    
        @await Component.InvokeAsync(typeof(WidgetStylesViewComponent))
        @await RenderSectionAsync("styles", false)
        @await Component.InvokeLayoutHookAsync(LayoutHooks.Head.Last, StandardLayouts.Account)
    
        <style>
            .lpx-login-bg {
                background-image: url('/LeptonX/images/login-pages/login-bg-img-@(selectedStyle).svg') !important;
            }
    
            :root .lpx-theme-light {
                @logoUrl
            }
            :root .lpx-theme-dark {
                @logoReverseUrl
            }
    
            :root .lpx-theme-dim {
                @logoReverseUrl
            }
        </style>
    </head>
    
    <body class="abp-account-layout lpx-theme-@selectedStyle">
    
        @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.First, StandardLayouts.Account)
    
    
        <div class="container-fluid p-0" style="overflow-x: hidden">
            @await Component.InvokeLayoutHookAsync(LayoutHooks.PageContent.First, StandardLayouts.Account)
            <div class="lpx-login-area">
                <div class="lpx-login-bg">
                    <div class="d-flex flex-column justify-content-center min-vh-100">
    
                        <div class="row">
                            <div class="col-xxl-5 col-lg-7 col-md-8 col-11 mx-auto position-relative py-4">
    
                                @if (BrandingProvider.LogoUrl.IsNullOrEmpty())
                                {
                                    <div class="lpx-logo-container lpx-login-brand-text">
                                        <div class="lpx-brand-logo lpx-login-logo mx-auto"></div> 
                                        <div class="lpx-brand-name lpx-login-name mx-auto">@BrandingProvider.AppName</div> 
                                    </div>
                                }
                                else
                                {
                                    <div class="lpx-brand-logo lpx-login-logo mb-3 mx-auto"></div>
                                }
    
    
                                <div class="card mx-auto" style="max-width: 30rem;">
                                    <div class="card-body p-3 p-sm-4">
                                        <div class="align-items-start d-flex justify-content-between mb-2">
                                            <h2 class="lpx-main-title lpx-login-title m-0 me-auto"> @PageLayout.Content.Title @* TODO: Find a better text here. *@</h2>
                                            <div class="dropdown btn-group ms-auto" aria-labelledby="languageDropdown">
    
                                                <button class="btn btn-sm btn-light dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
                                                    <i class="bi bi-translate me-1"></i> @languageInfo.CurrentLanguage.DisplayName
                                                </button>
    
                                                <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="dropdownMenuButton1" style="">
                                                    @foreach (var language in languageInfo.Languages)
                                                    {
                                                        var twoLetterLanguageName = new CultureInfo(language.CultureName).TwoLetterISOLanguageName.ToUpperInvariant();
                                                        var url = Url.Content($"~/Abp/Languages/Switch?culture={language.CultureName}&uiCulture={language.UiCultureName}&returnUrl={returnUrl}");
                                                        <li>
                                                            <a href="@url" class="dropdown-item" data-lpx-language-option="@twoLetterLanguageName">@language.DisplayName / @twoLetterLanguageName</a>
                                                        </li>
                                                    }
                                                </ul>
                                            </div>
                                        </div>
    
                                        <hr />
    
                                        @await Component.InvokeAsync(typeof(PageAlertsViewComponent))
    
                                        @* @if (MultiTenancyOptions.Value.IsEnabled &&
                                        (TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(CookieTenantResolveContributor.ContributorName)
                                        == true ||
                                        TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(QueryStringTenantResolveContributor.ContributorName)
                                        == true))
                                        {
                                            <div>
                                                <div class="row">
                                                    <div class="col">
                                                        <span style="font-size: .8em;"
                                                              class="text-uppercase text-muted">@MultiTenancyStringLocalizer["Tenant"]</span><br />
                                                        <h6 class="m-0 d-inline-block">
                                                            @if (CurrentTenant.Id == null)
                                                            {
                                                                <span>
                                                                    @MultiTenancyStringLocalizer["NotSelected"]
                                                                </span>
                                                            }
                                                            else
                                                            {
                                                                <strong>
                                                                    @(CurrentTenant.Name ??
                                                                        CurrentTenant.Id.Value.ToString())
                                                                </strong>
                                                            }
                                                        </h6>
                                                    </div>
                                                    <div class="col-auto">
                                                        <a id="AbpTenantSwitchLink" href="javascript:;"
                                                           class="btn btn-sm btn-outline-primary">@MultiTenancyStringLocalizer["Switch"]</a>
                                                    </div>
                                                </div>
                                            </div>
                                            <hr />
    
                                        } *@
    
                                        @RenderBody()
    
                                    </div>
                                    @* @await Html.PartialAsync("~/Themes/LeptonX/Layouts/Account/_Footer.cshtml") *@
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
    
            @await Component.InvokeLayoutHookAsync(LayoutHooks.PageContent.Last, StandardLayouts.Account)
        </div>
    
        <abp-script-bundle name="@LeptonXThemeBundles.Scripts.Global" />
        <script src="~/Abp/ApplicationLocalizationScript?cultureName=@CultureInfo.CurrentUICulture.Name"></script>
        <script type="text/javascript" src="~/Abp/ApplicationConfigurationScript"></script>
        <script type="text/javascript" src="~/Abp/ServiceProxyScript"></script>
        @await Component.InvokeAsync(typeof(WidgetScriptsViewComponent))
        @await RenderSectionAsync("scripts", false)
        @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.Last, StandardLayouts.Account)
    </body>
    
    </html>
    
    
  • User Avatar
    0
    jacek.bialy created

    it works for me.

    It works only when you are already on the login page and you modify the URL but when you are redirected from the portal page then it doesn't work for host (empty tenant). As I previously mentioned I'm using code below to correctly redirect user from portal to the auth server. PortalBlazorModule.cs

    options.Events.OnRedirectToIdentityProviderForSignOut = redirectContext =>
    {
         var currentTenant = redirectContext.HttpContext.RequestServices.GetRequiredService<ICurrentTenant>();
         redirectContext.ProtocolMessage.IssuerAddress += $"?__tenant={currentTenant?.Id}";
         return Task.CompletedTask;
    };
    

    You can override the account layout page.

    I'm using app-pro template, so probably the code that you provided won't work to hide the tenant field on login page?

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    I'm using app-pro template, so probably the code that you provided won't work to hide the tenant field on login page?

    It can hide the tenant field; you can give it a try.

    As I previously mentioned I'm using code below to correctly redirect user from portal to the auth server. PortalBlazorModule.cs

    It works only when you are already on the login page and you modify the URL but when you are redirected from the portal page then it doesn't work for host (empty tenant).

    You can try this

    app.Use(async (httpContext, next) =>
    {
        if (httpContext.Request.Path.StartsWithSegments("/Account/Login"))
        {
            var returnUrl = httpContext.Request.Query["ReturnUrl"].ToString();
            if(returnUrl.Contains("__tenant"))
            {
                var tenant = returnUrl.Split("__tenant=")[1].Split("&")[0];
                var queryBuilder = new QueryBuilder();
                foreach (var key in httpContext.Request.Query.Keys)
                {
                    queryBuilder.Add(key, httpContext.Request.Query[key].ToString());
                }
                queryBuilder.Add("__tenant", tenant);
                httpContext.Request.QueryString = queryBuilder.ToQueryString();
            }
        }
        await next();
    });
    
  • User Avatar
    0
    jacek.bialy created

    You can try this

    Yes, this code correctly adds _tenant parameter, that's half of the success :)

    It can hide the tenant field; you can give it a try.

    I put that razor code in correct location and now that version of code is displayed during login process. Unfortunately there are still 2 issues: 1. "Login" button is always disabled, even when I provide both user name and password. 2. Logo above login form is not displayed at all. There is only application name displayed but it looks like it's not affected by css style.

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer
    1. "Login" button is always disabled, even when I provide both user name and password.
    2. Logo above login form is not displayed at all. There is only application name displayed but it looks like it's not affected by css style.

    you can try add _ViewImports.cshtml

    @using System.Globalization
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
    @addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap
    @addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling
    

  • User Avatar
    0
    jacek.bialy created

    Now everything works fine :) I've got one question about ABP/LeptopX future updates. Now when I've got modified Default.cshtml file in my solution the only way to keep that file up to date is to compare it with newest LeptopX source? There is no automation possible right?

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Now when I've got modified Default.cshtml file in my solution the only way to keep that file up to date is to compare it with newest LeptopX source? There is no automation possible right?

    yes ,you need to do it. you can read the release logs to check if there chagned.

    https://abp.io/docs/latest/release-info/release-notes

  • User Avatar
    0
    jacek.bialy created

    Having that configuration I found two possible security issues: Steps to reproduce: - I'm logged in as user from tenant1 so my portal domain is tenant1-portal.com - I modify URL in browser to tenant2-portal.com - I'm redirected to portal main page with domain tenant2-portal.com - when I click Login button I'm automatically logged in, still as a tenant1 user but with url tenant2-portal.com That's the first issue. - then when I click Log out button it looks like I'm logged out out and redirected to portal main page - domain tenant2-portal.com - set URL to tenant1-portal.com - try to login - user is automatically logged in as tenant1 user (even though I logged out in previous steps) That's the second issue. Do you have any possible solutions for these issues?

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    This is because ABP always gets tenants from current users first. not a bug.

    https://abp.io/docs/latest/framework/architecture/multi-tenancy#determining-the-current-tenant

Made with ❤️ on ABP v9.1.0-preview. Updated on December 12, 2024, 07:15