Open Closed

ABP 10.2 User Shared Accounts Migration #10666


User avatar
0
cvarela@ld created

Hello!

We're migrating from ABP 10.1 to ABP 10.3 because we want to use the new User Shared Accounts feature. We were able to complete the migration successfully but we have two issues we would like some help with:

  1. After successful login we tried the "switch tenant" option but it fails with error 405. Here's a screenshot: . No errors in the logs, just this: [17:56:06 INF] Request starting HTTP/2 POST https://localhost:44337/Account/SwitchTenantLogin - application/x-www-form-urlencoded 105 [17:56:06 DBG] Starting resolving tenant... [17:56:06 DBG] Trying to resolve tenant through 'CurrentUser'... [17:56:06 DBG] Tenant resolved by 'CurrentUser' as 'Host'. [17:56:06 DBG] No tenant resolved. [17:56:06 DBG] Get dynamic claims cache for user: 49de4b5d-8fd0-c336-518d-3a213b12d893 [17:56:06 INF] Executing endpoint '405 HTTP Method Not Supported' [17:56:06 INF] Executed endpoint '405 HTTP Method Not Supported' [17:56:06 INF] Request finished HTTP/2 POST https://localhost:44337/Account/SwitchTenantLogin - 405 0 null 4.3153ms

  2. In the previous version we had a list of pre-defined users that were seeded to every tenant using IDataSeedContributor so when ever we created a tenant it will have some default users ready to use. This process now fails because users have to be globally unique, do you have any recommendation on how to achieve the same thing with this new version? I know we can invite the users to the tenant but that's a manual process and it requires that the user accepts the invitation in their email. We want the creation of these default users to be automatic.

Thank you in advance for your help!

Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

7 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • Issue #1 (Switch tenant → 405 on /Account/SwitchTenantLogin)

      In ABP, the tenant switch UI in the Account layout posts to a Razor Page endpoint. A 405 HTTP Method Not Supported typically means the POST target endpoint is not registered in the app that receives the request.

      In a tiered / separated auth server setup, the /Account/* endpoints are served by the AuthServer (or the project hosting the Account module UI), not by the Blazor Server UI project. Your log shows the request is going to:

      https://localhost:44337/Account/SwitchTenantLogin

      So the fix is: ensure the “Switch tenant” form/action points to the AuthServer host where the Account module is actually running (and that the AuthServer has the Account web pages enabled).

      Practical steps (what to check/change):

      1. Open the tenant switch from the login/account page that is rendered by your AuthServer, not from the Blazor UI host.
      2. Verify your Blazor UI project is not trying to serve /Account/* routes itself.
      3. If you customized the theme/layout or account pages, ensure the tenant switch posts to the ABP page endpoint (commonly /Abp/MultiTenancy/TenantSwitchModal in newer UI implementations) that exists in the AuthServer host.

      A verified related support thread shows the tenant switch UI is controlled from the theme Account layout, and demonstrates overriding it to post to /Abp/MultiTenancy/TenantSwitchModal (rather than a custom /Account/SwitchTenantLogin path) in LeptonX layout override:

      • https://abp.io/support/questions/9283
    • Issue #2 (Seeding “default users per tenant” with globally-unique users / Shared Accounts)

      With User Shared Accounts, the same user can be related to multiple tenants, but the user identity becomes globally unique, so the old approach “create the same usernames in every tenant” will conflict.

      Recommended approach to keep it automatic:

      1. Seed the default users only once in the Host context (globally).
      2. On each tenant creation (or tenant seed), programmatically associate/invite those existing host users to the new tenant rather than recreating them.

      Conceptually, this becomes a 2-step seed:

      • Global seed (host): create users support@..., ops@..., etc. once.
      • Per-tenant seed: add membership/assignment records that grant them access to the tenant.

      I don’t have a verified ABP 10.3 source/doc snippet in the available context that shows the exact service/API name to “auto-accept” an invitation (without email confirmation) for Shared Accounts in tiered Blazor Server. However, the correct direction is not duplicating IdentityUser records per tenant anymore—use the shared-account membership mechanism instead.

      Multi-tenancy background (verified):

      • https://abp.io/docs/10.3/framework/architecture/multi-tenancy/

    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.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    cvarela@ld created
    • Issue 1: I can confirm that https://localhost:44337/Account/SwitchTenantLogin is a post to the blazor server project. How do I make it post the auth server project?
    • Issue 2: This approach sounds good to me but I would need to know how to programmatically invite and accept users in the tenant.
    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    Two separate issues — let me address each.


    Issue 1: Switch Tenant returns 405

    The SwitchTenant modal in the Blazor Server / WebApp project posts to a relative path /Account/SwitchTenantLogin, which means the form is submitted to the Blazor host itself (port 44337 in your case). But the SwitchTenantLogin Razor Page is only registered on the AuthServer (the Volo.Abp.Account.Pro.Public.Web module), so the Blazor host has no handler and returns 405.

    The Blazor SwitchTenantModal already supports an absolute URL — it just needs AbpAccountSwitchTenantOptions.LoginUrl to be configured. The default app template doesn't wire this up (it does wire AbpAccountLinkUserOptions.LoginUrl right above, which is why Link Account works but Switch Tenant doesn't).

    Add this in your *BlazorModule.cs (or *BlazorServerModule.cs) ConfigureServices, next to the existing AbpAccountLinkUserOptions block:

    using Volo.Abp.Account.SwitchTenant;
    
    Configure<AbpAccountSwitchTenantOptions>(options =>
    {
        options.LoginUrl = configuration["AuthServer:Authority"];
    });
    

    Make sure your OIDC config has options.SaveTokens = true (the default template already has it), otherwise the access_token can't be attached to the form action.

    We'll fix the template to include this by default.


    Issue 2: Seeding default users to every new tenant under Shared Accounts

    The old approach (IDataSeedContributor seeds an admin/support/ops user into every new tenant) doesn't translate to Shared mode, and not just mechanically — it's incompatible with the design.

    Under Shared mode, an email/username pair identifies one person for the whole system. That person can belong to many tenants, but two different people can't share an email. In AbpUsers they show up as a host root row (TenantId = null) plus one tenant-shadow row per tenant they belong to — all those rows are the same identity. There's no such thing as "tenant A's support user" and "tenant B's support user" as two separate accounts; there is one support@yourcompany.com person, and tenants either have an association with that person or they don't.

    That's the reason SaaS removed the auto-create-admin behavior. TenantAppService.CreateAsync now publishes InviteUserToTenantRequestedEto { DirectlyAddToTenant = false }, which emails the admin and waits for them to opt in. Joining a tenant is the user's choice, not the system's. The invitation record is the audit trail of that choice.

    So the right pattern in Shared mode is:

    1. Create the support@yourcompany.com / ops@yourcompany.com users once in the host (a host-only IDataSeedContributor that calls IdentityUserManager.CreateAsync(...) with TenantId = null is fine, or let them self-register). This produces the single host root per identity that the model expects.
    2. Each tenant gets these users via the normal invitation flow — either you (the host admin) invite them from the SaaS host page after creating the tenant, or your operations workflow does it. The user accepts the invitation, and the tenant-shadow record gets created.

    If you bulk-create tenants and find clicking Invite for each one tedious, the realistic answer is to script the host-side action — call SaaS's InviteUserAsync (or publish InviteUserToTenantRequestedEto) right after your tenant-creation script for each default email. The user still opts in via the email; you've just automated the host-side invitation send, not the user's consent.

    I don't recommend writing a TenantCreatedEto handler that programmatically auto-accepts invitations on behalf of these users. The primitives to do it exist (UserInvitationManager.CreateAsync + UserSharingManager.AcceptInviteAsync is what UserSharingManager.AddPendingUserToTenantAsync uses internally for the DirectlyAddToTenant = true self-registration path), but applying them to a fixed default-user list means you're forging consent for every new tenant — which defeats the user-sovereignty guarantee the invitation flow was added to provide, and leaves no audit story when one of those users later changes role or leaves.

    I verified Issue 1 end-to-end on a fresh tiered Blazor Server template (ABP 10.3.0, Shared mode): the 405 disappears after wiring AbpAccountSwitchTenantOptions.LoginUrl.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    cvarela@ld created

    Hi Re: Issue 1: After adding that change it no longer attempts to send the post to the blazor server app but it doesn't work, after clicking switch it just shows the auth server . These are the logs: [22:58:39 ERR] Invalid RedirectUrl: https://localhost:44337/Account/Challenge, Use AppUrlProvider to configure it! [22:58:39 INF] Executed handler method OnPostAsync, returned result Microsoft.AspNetCore.Mvc.RedirectResult. [22:58:39 INF] Executing RedirectResult, redirecting to /. [22:58:39 INF] Executed page /Account/SwitchTenantLogin in 159.043ms [22:58:39 INF] Executed endpoint '/Account/SwitchTenantLogin' [22:58:39 INF] Request finished HTTP/2 POST https://localhost:44335/Account/SwitchTenantLogin?access_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IkI1MUNGRTg3N0QwNTE3NTE4MDZGQkM4RjdFNTU4MUMzOTZBMUU3NEIiLCJ4NXQiOiJ0UnotaDMwRkYxR0FiN3lQZmxXQnc1YWg1MHMiLCJ0eXAiOiJhdCtqd3QifQ.eyJpc3MiOiJodHRwczovL2xvY2FsaG9z... [22:58:39 INF] Request starting HTTP/2 GET https://localhost:44335/ - null null

    Re: Issue 2: Than you for the detailed explanation, I will attempt to implement the pattern you described.

    Thank you.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    That's the second layer of the Switch Tenant flow. Your log line is the giveaway:

    [ERR] Invalid RedirectUrl: https://localhost:44337/Account/Challenge, Use AppUrlProvider to configure it!
    

    After SwitchTenantLogin.OnPostAsync signs the user into the new tenant, it calls RedirectSafelyAsync(ReturnUrl, ...), which only allows return URLs whose origin is in AppUrlOptions.RedirectAllowedUrls. Your Blazor host https://localhost:44337 isn't in that list on the AuthServer side, so the redirect falls back to ~/ — the AuthServer home page, which is what you're seeing.

    The template wires the list from App:RedirectAllowedUrls in appsettings.json:

    // TestApp.AuthServer/<AuthServerModule>.cs
    Configure<AppUrlOptions>(options =>
    {
        options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
        options.RedirectAllowedUrls.AddRange(
            configuration["App:RedirectAllowedUrls"]?.Split(',') ?? Array.Empty<string>());
    });
    

    Fix

    In your AuthServer project's appsettings.json, add your Blazor host URL to App:RedirectAllowedUrls:

    {
      "App": {
        "SelfUrl": "https://localhost:44335",
        "RedirectAllowedUrls": "https://localhost:44337,...your existing entries..."
      }
    }
    

    Then restart the AuthServer. After the switch completes, the AuthServer will redirect back to https://localhost:44337/Account/Challenge and re-establish the Blazor session under the new tenant.

    While you're there, make sure the same Blazor URL is also in App:CorsOrigins (usually already added by the template). If you have other UI hosts pointing at this AuthServer, add their origins too — the check uses StartsWith, so a single entry like https://localhost:44337 covers any path under that origin.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    cvarela@ld created

    This worked. Thank you @maliming.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Great 👍

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
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.8.0-preview. Updated on September 16, 2026, 14:50
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.