Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f21ffe2
Add shared user account infrastructure.
maliming Dec 29, 2025
6e8301b
Merge branch 'dev' into Shared-User-Account
maliming Dec 30, 2025
87b1aa4
Merge branch 'dev' into Shared-User-Account
maliming Jan 2, 2026
32bc322
Fix tenant context when validating user uniqueness
maliming Jan 5, 2026
1a0b738
Add multi-tenant user lookup for email and username
maliming Jan 5, 2026
1cbf5aa
Merge branch 'dev' into Shared-User-Account
maliming Jan 5, 2026
d51a767
Add multi-tenant external login support for IdentityUser
maliming Jan 6, 2026
2192d90
Add GetUsersByPasskeyIdAsync method to identity user repositories
maliming Jan 7, 2026
4026313
Rename user lookup methods for shared user strategy
maliming Jan 7, 2026
168ea06
Add CreateTenantEto event class
maliming Jan 8, 2026
dbe0dbc
Improve host user selection logic in IdentityUserManager
maliming Jan 8, 2026
233f3ab
Add UserSharingStrategy to MultiTenancyInfoDto
maliming Jan 9, 2026
4d5307f
Refactor user validation logic in AbpIdentityUserValidator
maliming Jan 9, 2026
64abbf5
Add DirectlyAddToTenant property to InviteUserToTenantRequestedEto
maliming Jan 10, 2026
5a6a827
Merge branch 'dev' into Shared-User-Account
maliming Jan 16, 2026
a430c87
Update tests to expect AbpIdentityResultException on password change
maliming Jan 16, 2026
50e835a
Add tests for user repository multi-tenant and batch queries
maliming Jan 16, 2026
273e87f
Add tests for shared tenant user sharing strategy
maliming Jan 16, 2026
594b0bb
Add tests for shared user validation across tenants
maliming Jan 16, 2026
2394826
Add documentation for Shared User Accounts feature
maliming Jan 19, 2026
ae7c944
Optimised images with calibre/image-actions
github-actions[bot] Jan 19, 2026
af6e2a4
Merge branch 'dev' into Shared-User-Account
maliming Jan 21, 2026
582da14
Optimised images with calibre/image-actions
github-actions[bot] Jan 21, 2026
39dd491
Implement localized error messages for AbpIdentityResultException
maliming Jan 21, 2026
7683a83
Optimised images with calibre/image-actions
github-actions[bot] Jan 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/en/images/exist-user-accept.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/en/images/invite-user.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/en/images/manage-invitations.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/en/images/new-user-accept.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/en/images/new-user-join-strategy-inform.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/en/images/switch-tenant.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/en/images/tenant-selection.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/en/images/user-accepted.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions docs/en/modules/account-pro.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,3 +425,5 @@ This module doesn't define any additional distributed event. See the [standard d
* [Session Management](./account/session-management.md)
* [Idle Session Timeout](./account/idle-session-timeout.md)
* [Web Authentication API (WebAuthn) passkeys](./account/passkey.md)
* [Shared user accounts](./account/shared-user-accounts.md)
```
151 changes: 151 additions & 0 deletions docs/en/modules/account/shared-user-accounts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
```json
//[doc-seo]
{
"Description": "Learn how Shared User Accounts work in ABP (UserSharingStrategy): login flow with tenant selection, switching tenants, inviting users, and the Pending Tenant registration flow."
}
```

# Shared User Accounts

This document explains **Shared User Accounts**: a single user account can belong to multiple tenants, and the user can choose/switch the active tenant when signing in.

> This is a **commercial** feature. It is mainly provided by Account.Pro and Identity.Pro (and related SaaS UI).

## Introduction

In a typical multi-tenant setup with the **Isolated** user strategy, a user belongs to exactly one tenant (or the Host), and uniqueness rules (username/email) are usually scoped per tenant.

If you want a `one account, multiple tenants` experience (for example, inviting the same email address into multiple tenants), you should enable the **Shared** user strategy.

## Enabling Shared User Accounts

Enable shared accounts by configuring `AbpMultiTenancyOptions.UserSharingStrategy`:

```csharp
Configure<AbpMultiTenancyOptions>(options =>
{
options.IsEnabled = true;
options.UserSharingStrategy = TenantUserSharingStrategy.Shared;
});
```

### Constraints and Behavior

When you use Shared User Accounts:

- Username/email uniqueness becomes **global** (Host + all tenants). A username/email can exist only once, but that user can be invited into multiple tenants.
- Some security/user management settings (2FA, lockout, password policies, recaptcha, etc.) are managed at the **Host** level.

If you are migrating from an isolated strategy, ABP will validate the existing data when you switch to Shared. If there are conflicts (e.g., the same email registered as separate users in different tenants), you must resolve them before enabling the shared strategy. See the [Migration Guide](#migration-guide) section below.

## Tenant Selection During Login

If a user account belongs to multiple tenants, the login flow prompts the user to select the tenant to sign in to:

![Tenant Selection](../../images/tenant-selection.png)

## Switching Tenants

After signing in, the user can switch between the tenants they have joined using the tenant switcher in the user menu:

![Tenant Switching](../../images/switch-tenant.png)

## Leaving a Tenant

Users can leave a tenant. After leaving, the user is no longer a member of that tenant, and the tenant can invite the user again later.

> When a user leaves and later re-joins the same tenant, the `UserId` does not change and tenant-related data (roles, permissions, etc.) is preserved.

## Inviting Users to a Tenant

Tenant administrators can invite existing or not-yet-registered users to join a tenant. The invited user receives an email; clicking the link completes the join process. If the user doesn't have an account yet, they can register and join through the same flow.

While inviting, you can also assign roles so the user gets the relevant permissions automatically after joining.

> The invitation feature is also available in the Isolated strategy, but invited users can join only a single tenant.

![Invite User](../../images/invite-user.png)

## Managing Invitations

From the invitation modal, you can view and manage sent invitations, including resending an invitation email and revoking individual or all invitations.

![Manage Invitations](../../images/manage-invitations.png)

## Accepting an Invitation

If the invited person already has an account, clicking the email link shows a confirmation screen to join the tenant:

![Accept Invitation](../../images/exist-user-accept.png)

If the invited person doesn't have an account yet, clicking the email link takes them to registration and then joins them to the tenant:

![Accept Invitation New User](../../images/new-user-accept.png)

After accepting the invitation, the user can sign in and switch to that tenant.

![Accepted Invitation](../../images/user-accepted.png)

## Inviting an Admin After Tenant Creation

With Shared User Accounts, you typically don't create an `admin` user during tenant creation. Instead, create the tenant first, then invite an existing user (or a new user) and grant the required roles.

![Invite tenant admin user](../../images/invite-admin-user-to-join-tenant.png)

![Invite tenant admin user](../../images/invite-admin-user-to-join-tenant-modal.png)

> In the Isolated strategy, tenant creation commonly seeds an `admin` user automatically. With Shared User Accounts, you usually use invitations instead.

### Registration Strategy for New Users

When a user registers a new account, the user is not a member of any tenant by default (and is not a Host user). You can configure `AbpIdentityPendingTenantUserOptions.Strategy` to decide what happens next.

Available strategies:

- **CreateTenant**: Automatically creates a tenant for the new user and adds the user to that tenant.
- **Redirect**: Redirects the user to a URL where you can implement custom logic (commonly: a tenant selection/join experience).
- **Inform** (default): Shows an informational message telling the user to contact an administrator to join a tenant.

> In this state, the user can't proceed into a tenant context until they follow the configured strategy.

### CreateTenant Strategy

```csharp
Configure<AbpIdentityPendingTenantUserOptions>(options =>
{
options.Strategy = AbpIdentityPendingTenantUserStrategy.CreateTenant;
});
```

![new-user--join-strategy-create-tenant](../../images/new-user-join-strategy-create-tenant.png)

![new-user--join-strategy-create-tenant-success](../../images/new-user-join-strategy-create-tenant-success.png)

### Redirect Strategy

```csharp
Configure<AbpIdentityPendingTenantUserOptions>(options =>
{
options.Strategy = AbpIdentityPendingTenantUserStrategy.Redirect;
options.RedirectUrl = "/your-custom-logic-url";
});
```

### Inform Strategy

```csharp
Configure<AbpIdentityPendingTenantUserOptions>(options =>
{
options.Strategy = AbpIdentityPendingTenantUserStrategy.Inform;
});
```

![new-user--join-strategy-inform](../../images/new-user-join-strategy-inform.png)

## Migration Guide

If you plan to migrate an existing multi-tenant application from an isolated strategy to Shared User Accounts, keep the following in mind:

1. **Uniqueness check**: Before enabling Shared, ensure all existing usernames and emails are unique globally. ABP performs this check when you switch the strategy and reports conflicts.
2. **Tenants with separate databases**: If some tenants use separate databases, you must ensure the Host database contains matching user records in the `AbpUsers` table (and, if you use social login / passkeys, also sync `AbpUserLogins` and `AbpUserPasskeys`) so the Host-side records match the tenant-side data. After that, the framework can create/manage the user-to-tenant associations.

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Ddd.Application.Contracts\Volo.Abp.Ddd.Application.Contracts.csproj" />
<ProjectReference Include="..\Volo.Abp.MultiTenancy.Abstractions\Volo.Abp.MultiTenancy.Abstractions.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using Volo.Abp.Application;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;

namespace Volo.Abp.AspNetCore.Mvc;

[DependsOn(
typeof(AbpDddApplicationContractsModule)
)]
typeof(AbpDddApplicationContractsModule),
typeof(AbpMultiTenancyAbstractionsModule)
)]
public class AbpAspNetCoreMvcContractsModule : AbpModule
{

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
namespace Volo.Abp.AspNetCore.Mvc.MultiTenancy;
using Volo.Abp.MultiTenancy;

namespace Volo.Abp.AspNetCore.Mvc.MultiTenancy;

public class MultiTenancyInfoDto
{
public bool IsEnabled { get; set; }

public TenantUserSharingStrategy UserSharingStrategy { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ protected virtual MultiTenancyInfoDto GetMultiTenancy()
{
return new MultiTenancyInfoDto
{
IsEnabled = _multiTenancyOptions.IsEnabled
IsEnabled = _multiTenancyOptions.IsEnabled,
UserSharingStrategy = _multiTenancyOptions.UserSharingStrategy
};
}

Expand Down
Loading