* `TenantAdminUserName` (default: admin): The tenant admin user name.
* `TenantAdminUserName` (default: admin): The tenant admin user name.
* `ImpersonationTenantPermission`: The permission name for tenant impersonation.
* `ImpersonationTenantPermission`: The permission name for tenant impersonation.
* `ImpersonationUserPermission`: The permission name for user impersonation.
* `ImpersonationUserPermission`: The permission name for user impersonation.
* `ExternalProviderIconMap`: A dictionary of external provider names and their corresponding font-awesome icon classes. You can add new mapping to this dictionary to change the icon of an external provider.(Popular external provider icons are already defined, such as `Facebook`, `Google`, `Microsoft`, `Twitter`, etc.)
* `SwitchUserDuringImpersonate` (default: `false`): Signs the target user in with the application cookie while an impersonation flow is in progress.
* `ExternalProviderIconMap`: A dictionary of external provider names and their icon asset paths or CSS classes. Common providers such as GitHub, Google, X, Apple, LinkedIn, Facebook and Microsoft are already mapped.
* `IsTenantMultiDomain` (default: `false`): Enables tenant-domain redirects for linked-account and tenant-switching flows.
* `GetTenantDomain`: Resolves the target tenant's origin for impersonation redirects and, when `IsTenantMultiDomain` is enabled, linked-account and tenant-switching redirects. By default, it returns the current request's scheme and host.
* `ExternalProfilePictureDownloadTimeout` (default: 5 seconds): Limits how long external-login registration waits while downloading a profile picture.
* `EnableImageCompression` (default: false): Enables the image compression for the profile picture. When enabled, the selected compression library will compress the profile picture to decrease the image size. For more information see [image manipulation](../framework/infrastructure/image-manipulation.md)
* `EnableImageCompression` (default: `false`): Enables image compression for the profile picture. When enabled, the selected compression library compresses the profile picture to decrease its size. For more information, see [image manipulation](../framework/infrastructure/image-manipulation.md).
* `AllowedFileExtensions` (default: `.jpg`, `.jpeg` and `.png`): Defines the accepted file-name extensions. The extension check runs when the upload includes a file name.
* `MaxFileSizeInBytes` (default: 5 MiB): Rejects larger uploads. Set it to `0` to disable the size limit.
* `MagicBytesVerifiers`: Verifies the file content independently of the file name. The default verifiers accept JPEG and PNG signatures. If you add an allowed extension, add a matching content verifier as well; at least one verifier must accept every uploaded image.
### Registration Email Confirmation Codes
The registration email confirmation code is stored with a 10-minute absolute expiration by default. Configure a different duration with `Account:EmailConfirmation:CodeExpirationTime`:
```json
{
"Account": {
"EmailConfirmation": {
"CodeExpirationTime": "00:15:00"
}
}
}
```
Sending and checking these codes use separate built-in operation rate-limit policies. Sending a new code resets the check rate-limit state for that email address.
## Local login
## Local login
@ -111,6 +134,20 @@ If you use `Social / External Logins`, It is automatically called for authentica
Email login lets users sign in with a one-time code, a magic link or both. It is disabled by default and also requires **Local login** to remain enabled. Configure it in `Settings > Account > Email Login`.
The available login types are:
* `OtpAndMagicLink` (default): The email contains both a six-digit code and a magic link.
* `MagicLinkOnly`: The email contains only a magic link and the code-verification endpoint is disabled.
* `OtpOnly`: The email contains only a code and direct magic-link verification is disabled.
The token lifespan defaults to 90 seconds and accepts values from 30 to 86,400 seconds. Codes and link tokens are single-use. Completing either path invalidates the outstanding credential for the other path, and sending a new email invalidates the previous credentials.
Email login uses built-in send and verification rate limits. When `AccountSettingNames.PreventEmailEnumeration` is enabled, requests for an unknown or locked-out account return the same expiry-shaped response as a valid request without sending an email. This prevents callers from using the send response to distinguish those accounts.
### Switching users during OAuth login
### Switching users during OAuth login
If you have an OAuth/Auth Server application using the Account Pro module, you can pass the `prompt=select_account` parameter to force the user to select an account.
If you have an OAuth/Auth Server application using the Account Pro module, you can pass the `prompt=select_account` parameter to force the user to select an account.
You can modify the look and behavior of the module pages by passing the following options to `createRoutes` static method:
You can modify the look and behavior of the module pages by passing the following options to `createRoutes` static method:
- **redirectUrl**: Default redirect URL after logging in.
- **redirectUrl**: Default redirect URL after logging in.
- **entityActionContributors:** Changes grid actions. Please check [Entity Action Extensions for Angular](../framework/ui/angular/entity-action-extensions.md) for details.
- **entityActionContributors:** Changes actions on `eAccountComponents.MySecurityLogs`. See [Entity Action Extensions for Angular](../framework/ui/angular/entity-action-extensions.md).
- **toolbarActionContributors:** Changes page toolbar. Please check [Page Toolbar Extensions for Angular](../framework/ui/angular/page-toolbar-extensions.md) for details.
- **toolbarActionContributors:** Changes the toolbar on `eAccountComponents.MySecurityLogs`. See [Page Toolbar Extensions for Angular](../framework/ui/angular/page-toolbar-extensions.md).
- **entityPropContributors:** Changes table columns. Please check [Data Table Column Extensions for Angular](../framework/ui/angular/data-table-column-extensions.md) for details.
- **entityPropContributors:** Changes columns on `eAccountComponents.MySecurityLogs`. See [Data Table Column Extensions for Angular](../framework/ui/angular/data-table-column-extensions.md).
- **personelInfoEntityPropContributors:** Changes the edit-form properties on `eAccountComponents.PersonalSettings`. The public API uses this spelling. See [Dynamic Form Extensions for Angular](../framework/ui/angular/dynamic-form-extensions.md).
- **isPersonalSettingsChangedConfirmationActive:** Deprecated. Personal settings refresh the current user's state without requiring a new login.
#### Services / Models
#### Services / Models
@ -426,4 +465,3 @@ This module doesn't define any additional distributed event. See the [standard d
Passkeys are disabled by default. The default limit is 10 passkeys per user, and the configured limit must be greater than zero. Registration is rejected when passkeys are disabled or the user has reached the limit.
## Manage Passkeys
## Manage Passkeys
You can add/rename/delete your passkeys in the `Account/Manage` page:
You can add/rename/delete your passkeys in the `Account/Manage` page:
Invitation links contain a protected, URL-safe token and expire after 7 days by default. Invalid, modified or expired tokens are rejected. Configure the lifespan with `UserInvitationTokenProviderOptions`:
Inviting the same email address again while an invitation is still pending reuses that invitation, replaces its assigned roles and refreshes its invitation date. Resending is allowed only for a pending invitation; it refreshes the invitation date and sends a newly generated token.
## Accepting an Invitation
## Accepting an Invitation
If the invited person already has an account, clicking the email link shows a confirmation screen to join the tenant:
If the invited person already has an account, clicking the email link shows a confirmation screen to join the tenant:
@ -150,13 +161,13 @@ When the Shared strategy is enabled, a user is a **global resource** across host
### Host-only operations
### Host-only operations
The following operations can only be performed by a host administrator when Shared is enabled. Both the Identity Pro UI (MVC + Blazor) and the `IdentityUserAppService` enforce this — a direct API call from a tenant context will be rejected with a `UserFriendlyException`:
The following operations can only be performed by a host administrator when Shared is enabled. Both the Identity Pro UI (MVC + Blazor) and the `IdentityUserAppService` enforce these restrictions.
- Delete a user
- Delete a user
- Activate / deactivate a user (`IsActive`)
- Lock / unlock a user
- Lock / unlock a user
- Enable or disable two-factor authentication
- Enable or disable two-factor authentication
- Change `LockoutEnabled` or `ShouldChangePasswordOnNextLogin`
Direct tenant API calls for these operations are rejected with a `UserFriendlyException`. When a tenant update request changes `IsActive`, `LockoutEnabled` or `ShouldChangePasswordOnNextLogin`, the application service restores the current host-managed values and continues processing the remaining editable fields.
> `Delete` here means deleting the **global user account**, not removing a user from a single tenant. Removing a member from one tenant is a tenant-level soft operation and is available to tenant administrators — see **Remove from tenant** below.
> `Delete` here means deleting the **global user account**, not removing a user from a single tenant. Removing a member from one tenant is a tenant-level soft operation and is available to tenant administrators — see **Remove from tenant** below.
@ -176,6 +187,4 @@ Users can leave a tenant from their own account menu (`Switch Tenant` → `Leave
If you plan to migrate an existing multi-tenant application from an isolated strategy to Shared User Accounts, keep the following in mind:
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.
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.
2. **Tenants with separate databases**: The module detects a separate Identity database by comparing the resolved Identity connection string for the tenant with the host connection string. If some tenants use separate databases, ensure that the host database contains the corresponding shadow users in the `AbpUsers` table. Host-side shadow users are located by the tenant identifier and email address. If you use social login or passkeys, also synchronize `AbpUserLogins` and `AbpUserPasskeys`. Existing shadow rows that reuse the tenant user's primary key remain compatible with leaving a tenant.
- **Important — each host-side shadow row must have a new primary key (`Id`) different from the tenant user's `Id`.** Generate a fresh `Guid` for every shadow row instead of reusing the tenant user's primary key. The framework relies on this to distinguish a separate-database tenant from a shared-database one; reusing the Id can mask "Leave Tenant" and external login / passkey synchronization on legacy data. The other identifying fields (`UserName`, `Email`, `PasswordHash`, `TenantId`, etc.) should still match the tenant-side row.
Before creating an account, the import workflow searches by username and email. If it finds an external user, the selected provider updates that user. If it finds a local user, the import is rejected instead of converting or overwriting the local account.
With [Shared User Accounts](../account/shared-user-accounts.md), this lookup covers the Host and all tenants. A Host-context import therefore cannot create a second identity for a username or email that already belongs to another tenant.
@ -25,6 +25,25 @@ Then you can enter the user name and password on the login page for oauth extern


## Mapping User Claims
`AbpOAuthExternalLoginProviderOptions` maps claims returned by the user-info endpoint to the imported or updated Identity user. The defaults use ABP claim types for name, surname, email, email verification, phone number, phone verification and user id. Configure the mappings when the provider returns different claim names:
"Description": "Learn how to configure password reuse prevention and password history retention in the ABP Identity Pro module."
}
```
# Password History
# Password History
## Introduction
## Introduction
> You must have an ABP Team or a higher license to use this module & its features.
> You must have an ABP Team or a higher license to use this module & its features.
The Identity PRO module has a built-in password history function that allows you to enforce password reuse policies for users within your application. It keeps track of users’ previously used passwords and checks this history whenever a user attempts to change their password. This prevents users from setting a password that they have already used in the past, ensuring that each new password is unique and not a repetition of an older one.
The Identity Pro module has a built-in password history function that allows you to enforce password reuse policies for users within your application. It keeps hashes of previously used passwords and checks the configured history window whenever a user or administrator changes or resets a password.
## Password History Settings
## Password History Settings
@ -13,8 +20,25 @@ You need to enable the password history and configure related settings:
* **Enable prevent password reuse**: Whether to prevent users from reusing their previous passwords.
* **Enable prevent password reuse**: Whether to prevent users from reusing their previous passwords.
* **Password change period**: The number of previous passwords that cannot be reused.
* **Prevent password reuse count**: The number of previous passwords that cannot be reused. The settings API accepts values from `1` through `128`.
When you enable the password history, users and administrators will not be able to reuse their previous passwords when changing/resetting their passwords.
When you enable password history with a positive reuse count, users and administrators cannot reuse a password in that window when changing or resetting a password.
By default, the module keeps only the number of records configured by **Prevent password reuse count**. Set `AbpIdentityPasswordHistoryOptions.KeepAllRecords` to `true` if your retention policy requires every recorded password hash to remain in the database:
`KeepAllRecords` changes database retention only. Password validation still checks only the configured reuse-count window.
History pruning is evaluated after a successful password add, change or reset records a new hash. Lowering **Prevent password reuse count** does not immediately delete existing history records.
> The settings UI and API do not accept `0`. If the underlying setting is bypassed and written as `0`, reuse validation is disabled, but new history records continue to be stored and are not pruned by the reuse count.
@ -50,7 +50,7 @@ The `IdentitySessionCleanupBackgroundWorker` is a background worker that will re
## How it works
## How it works
This feature depends on the [Dynamic Claims](https://docs.abp.io/en/abp/latest/Dynamic-Claims) feature of the ABP framework. Here is how it works:
This feature depends on the [Dynamic Claims](../../framework/fundamentals/dynamic-claims.md) feature of the ABP framework. Here is how it works:
* The `IdentitySessionClaimsPrincipalContributor` will generate a random GUID as a `sessionid` to add the `ClaimsPrincipal`, This usually happens when logging in to get the user's claims.
* The `IdentitySessionClaimsPrincipalContributor` will generate a random GUID as a `sessionid` to add the `ClaimsPrincipal`, This usually happens when logging in to get the user's claims.
* The `OnSignedIn` event of `Identity` and `ProcessSignIn` event of `OpenIddict` will get this `sessionid` and store it in the database (`IdentitySession` table).
* The `OnSignedIn` event of `Identity` and `ProcessSignIn` event of `OpenIddict` will get this `sessionid` and store it in the database (`IdentitySession` table).
@ -61,4 +61,4 @@ This feature depends on the [Dynamic Claims](https://docs.abp.io/en/abp/latest/D
* The `IdentitySessionCleanupBackgroundWorker` will remove the inactive sessions.
* The `IdentitySessionCleanupBackgroundWorker` will remove the inactive sessions.
* Once a new session has been created, we will remove the other sessions based on the `PreventConcurrentLogin` setting.
* Once a new session has been created, we will remove the other sessions based on the `PreventConcurrentLogin` setting.
* The `IdentitySessionManager` is used to manage/maintain the sessions. Please use this class instead of directly using the repository.
* The `IdentitySessionManager` is used to manage/maintain the sessions. Please use this class instead of directly using the repository.
* The `UpdateSessionAfterCacheHit(10 times by default)` property of `IdentitySessionCheckerOptions`is used to configure the `IdentitySessionChecker` that will update session in the database when cache hits reach this value. This is to reduce the database update frequency and improve performance.
* The `UpdateSessionAfterCacheHit` property of `IdentitySessionCheckerOptions`controls periodic database updates after cache hits and defaults to `10`. The checker writes on the first successful check, then writes again when the hit count becomes greater than the configured value and resets the counter. This reduces the database update frequency.