Activities of "batuhankara"

SHORT QUESTION — GET /api/account/user-sharing only returns 1 of 2 tenants

Setup: ABP 10.3.0 commercial, .NET 10, database-per-tenant, TenantUserSharingStrategy.Shared.

Goal: one shared user logs in once, lists their tenants, then uses SwitchTenant to switch by TenantId.

Problem: A user invited into two tenants can log in and SwitchTenant into both, but GET /api/account/user-sharing returns only one tenant.

Cause we found: in the host DB there is no TenantId = NULL master row for this user — only two per-tenant association rows. The admin user has a NULL-tenant row; this invited user does not. Host AbpUsers for the user:

| Id | TenantId | IsActive | Leaved | IsDeleted | |---|---|---|---|---| | 01524844… | dev tenant id | 1 | 0 | 0 | | f8bb49c3… | batu tenant id | 1 | 0 | 0 | | (no row) | NULL | — | — | — |

UserSharingAppService.GetAllListAsync → GetUserWithTenantsFromHostAsync enumerates the user's tenants from the host master (TenantId = NULL) row; with no master, it returns only the current tenant.

We are not manually editing any rows — we expect ABP's own invite/accept flow to create the host master row for us. Our questions:

  1. We invited the user via the Saas tenant invite-user flow (POST /api/saas/tenants/{id}/invite-user) and accepted via POST /api/account/user-sharing/invitation/accept. This created the per-tenant rows but no TenantId = NULL master row. Should this flow have created the host master automatically? Is this the wrong invite endpoint for Shared accounts (i.e. we should be calling a different built-in invite that runs UserSharingManager.CreateUserInHostAsync), or is it a bug that the master row is not created?
  2. What is the correct built-in invite/accept path for Shared + db-per-tenant so the host master row is created by ABP, with no manual steps on our side?
  3. For users already in this broken state, is there a supported manager call to backfill the host master row, so /api/account/user-sharing lists all their tenants?

FOLLOW-UP (after applying the TokenController workaround)

We applied the password-grant workaround (overriding TokenController.HandlePasswordAsync to resolve the user across tenants from the Host context via UserSharingManager.GetUsersByUserNameFromHostAsync / GetUsersByEmailFromHostAsync, then sign in under user.TenantId). Login at /connect/token now works and a tenant-scoped token is issued. Thank you.

Two follow-up issues remain:

1) EntityNotFoundException in IdentityDynamicClaimsPrincipalContributor after login

On the token request and on subsequent authenticated calls, we see this repeatedly:

[WRN] User not found: 01524844-e59b-7751-25a3-3a21e45548bf
Volo.Abp.Domain.Entities.EntityNotFoundException: There is no such an entity.
  Entity type: Volo.Abp.Identity.IdentityUser, id: 01524844-e59b-7751-25a3-3a21e45548bf
   at Volo.Abp.Identity.IdentityUserManager.GetByIdAsync(Guid id)
   ...
   at Volo.Abp.Identity.IdentityDynamicClaimsPrincipalContributorCache.GetAsync(Guid userId, Nullable`1 tenantId)
   at Volo.Abp.Identity.IdentityDynamicClaimsPrincipalContributor.ContributeAsync(...)

The id 01524844-... is the invited user's tenant row (it exists in the tenant DB, not the host DB). The dynamic-claims contributor appears to look the user up in a context/DB where that id is not found (host/db-per-tenant), so it throws and the dynamic claims (roles/permissions) are not contributed.

After login, /api/abp/application-configuration returns:

"auth": { "grantedPolicies": {} },
"currentUser": { "isAuthenticated": false, "roles": [] }

i.e. the user has a valid token but no roles/permissions (dynamic claims didn't load).

Question: Is IdentityDynamicClaimsPrincipalContributor shared-account/db-per-tenant aware in 10.3.0? How should we make dynamic claims resolve the user in the correct tenant DB for a shared user, so the EntityNotFoundException stops and the user's roles/permissions are populated?

Environment note

  • ABP 10.3.0, database-per-tenant, TenantUserSharingStrategy.Shared, dynamic claims enabled (IsDynamicClaimsEnabled = true).

UPDATE — the core problem: one shared user, multiple tenants, different roles per tenant (db-per-tenant)

What we are trying to build (the goal)

One user account must have access to multiple tenants, with a different role in each tenant.

Concretely: a supplier's user (home tenant = "dev") is invited as a guest into a sourcing company's tenant ("batu"). They keep one login. In "dev" they have their normal role; in "batu" they get a restricted "External QC" role. They log in once, then switch tenant in-session to move between workspaces. This is exactly the use case Shared User Accounts seems designed for, on a database-per-tenant deployment.

The data we observe for one shared user (batuhan@linkse.io)

After inviting + accepting this user into two tenants, here are all the AbpUsers rows for them:

| Database | TenantId | IdentityUser.Id | EmailConfirmed | |---|---|---|---| | Host DB — dev association | dev e2d504fe | 01524844-… | 0 | | Host DB — batu association | batu a0cadb7b | F8BB49C3-… | 0 | | dev tenant DB | dev e2d504fe | 3751F3BB-… | 1 | | batu tenant DB | batu a0cadb7b | 9488521D-… | 1 |

So for the same human in the same tenant, there are two different IdentityUser.Id values: a host-side association row and the tenant-DB row. They are matched only by normalized username/email — the Ids differ across databases.

The central issue — the token's sub is the HOST association id, but every tenant API resolves against the TENANT DB id

  1. Login under Shared resolves /connect/token to Host and signs the user in under their host association row. The issued token's sub (= AbpClaimTypes.UserId) is therefore the host-association id (e.g. 01524844).

  2. Every authenticated request then runs in the tenant context, where the user's id is different (3751F3BB in dev's DB). So:

    • Dynamic claims: IdentityDynamicClaimsPrincipalContributorCache.GetAsync(sub, tenantId) does GetByIdAsync(01524844) in the tenant DB → EntityNotFoundException → empty grantedPolicies (no permissions). (This is issue #1 above.)
    • Any endpoint that reads sub directly (e.g. GET /api/identity/users/{sub}, user profile, GET /api/account/user-sharing) → 404 "There is no entity IdentityUser with id = 01524844…", because the tenant DB only knows 3751F3BB.

    This is not one buggy endpoint — it is a systemic mismatch: the token carries the host id, tenant-context code expects the tenant-DB id.

How we are currently working around it (and why it feels wrong)

We have had to add two [Dependency(ReplaceServices)] overrides to get a shared user logged in and permissioned at all:

  1. SharedAwareTokenController ([ReplaceControllers(typeof(TokenController))]): the stock password grant only matches host accounts (TenantId == null), so an invited+accepted user with only tenant-association rows is reported "no user found" → invalid_grant. Our override resolves the user across tenants via UserSharingManager.GetUsersByUserNameFromHostAsync / GetUsersByEmailFromHostAsync, signs in under the user's tenant, and auto-confirms the email (the invitation already proved ownership). This fixed login.

  2. SharedAwareDynamicClaimsCache ([ExposeServices(typeof(IdentityDynamicClaimsPrincipalContributorCache))]): to fix empty permissions, we translate the host-association id → tenant-DB id by matching on NormalizedUserName inside the target tenant, then call base.GetAsync(tenantRow.Id, targetTenantId). This fixed permissions.

These two get login + permissions working, but they only patch the dynamic-claims path. Every other endpoint that reads sub directly still 404s, because sub is still the host id. Patching endpoint-by-endpoint is clearly the wrong layer.

Question A — what is the intended sub for a shared user under db-per-tenant?

Under Shared + database-per-tenant, which id is the access token's sub supposed to be — the host-association id or the tenant-DB id? If it is supposed to be the host id, how is tenant-context code (dynamic claims, IIdentityUserAppService.GetAsync, profile, user-sharing) meant to resolve the correct tenant-DB user from it? Is there a built-in id-translation we are missing, or is the intended design that the host and tenant rows share the same Id (and our data is wrong because invite/accept created differing Ids)?

If host and tenant rows are supposed to share the same Id, what is the supported way to create a shared membership so the tenant-DB row reuses the host id? (We invited via the Saas tenant invite-user flow and via UserInvitationManager — both produced differing Ids.)

Question B — tenant switching for a headless SPA (no MVC UI)

We need the user to switch tenant after login. We found that AbpAccountPublicWebOpenIddictModule registers the "SwitchTenant" flow name (GrantTypes.Add("SwitchTenant")) but does not add a handler to AbpOpenIddictExtensionGrantsOptions.Grants (it adds LinkLogin and Impersonation, but not SwitchTenant — it appears wired only to the MVC SwitchTenantLoginModel page). So POST /connect/token with grant_type=SwitchTenant returns "The specified grant type SwitchTenant is not implemented."

We worked around it by registering the handler ourselves:

options.Grants.TryAdd(
    SwitchTenantExtensionGrant.ExtensionGrantName,        // "SwitchTenant"
    new Volo.Abp.Account.Web.ExtensionGrants.SwitchTenantExtensionGrant());

Is this the supported way to enable headless SwitchTenant token exchange for an SPA, or is there an intended API/grant for "switch the current shared user into another of their tenants"? And after a switch, will the new token's sub be the target tenant's id (so tenant APIs resolve), or again a host-association id (re-triggering Question A in the target tenant)?

Question C — different roles per tenant

The user must have different roles in each tenant (normal role in dev, "External QC" in batu). With Shared accounts + db-per-tenant, is per-tenant role assignment simply "assign roles on the tenant-DB row" (each tenant DB has its own AbpUserRoles), and is that the supported model? The Saas invite-user flow assigns no roles; UserInvitationManager.CreateAsync has AssignedRoles — is that the recommended channel for per-tenant guest roles, and does it work with DirectlyAddToTenant under Shared?

Summary of what we need from you

  1. The intended sub/id model for a shared user under database-per-tenant, so we can stop patching id-translation in dynamic claims and (worse) per-endpoint.
  2. The supported headless tenant-switch mechanism for an SPA (is manual SwitchTenantExtensionGrant registration correct?).
  3. The supported per-tenant role assignment path for invited guests.

If the differing-Id-per-database situation is itself the bug (host and tenant rows should share one Id), please tell us the correct provisioning call so we can fix the data instead of the framework.

Our environment recap

  • ABP 10.3.0 commercial (Identity.Pro, Account.Pro, Saas), .NET 10, database-per-tenant.
  • TenantUserSharingStrategy.Shared, dynamic claims enabled, Redis distributed lock configured.
  • Custom tenant header linksetenantid (options.TenantKey).
  • Encrypted (JWE) tokens.

Shared User Accounts: invited+accepted user cannot log in ("Invalid username or password")

Environment

  • ABP version: 10.3.0 (commercial — Identity.Pro, Account.Pro, Saas)
  • .NET: 10
  • UI: none (backend API only; testing via Postman)
  • Multi-tenancy: enabled, database-per-tenant (each tenant has its own DB; host is a separate DB)
  • User sharing strategy: TenantUserSharingStrategy.Shared
  • Distributed lock: configured (Redis) — required by Shared strategy
  • Custom tenant key (header): options.TenantKey = "linksetenantid"

Relevant config:

// Domain module
Configure<AbpMultiTenancyOptions>(options =>
{
    options.IsEnabled = true;
    options.UserSharingStrategy = TenantUserSharingStrategy.Shared;
});

// HttpApi.Host module
Configure<AbpAspNetCoreMultiTenancyOptions>(options =>
{
    options.TenantKey = "linksetenantid"; // custom header name instead of __tenant
});

What I did (steps to reproduce)

  1. Created the host and a tenant named "Dev" (database-per-tenant; Dev has its own database).
  2. Logged in to the host as the default admin.
  3. Invited an existing user (userA — email userA@example.com) into tenant Dev using the invitation feature.
  4. Received the invitation email and accepted it via the user-sharing accept endpoint:

POST /api/account/user-sharing/invitation/accept Content-Type: application/json { "token": "

The accept returned success. (Before accepting, `GET /api/account/user-sharing/invitation?token=...`
returned `requireRegister: true`, `tenantName: "Dev"`, `inviteeEmail: "userA@example.com"`.)

## What I see in the databases after accept
- In the **host** database `AbpUsers`: I see a record for `userA`, **but it has a non-null `TenantId`** (the Dev tenant's id) — I did **not** see a `TenantId = NULL` (host) record for this user.
- In the **Dev tenant** database `AbpUsers`: I see 1 record for `userA` (with the Dev `TenantId`).

(Question: under Shared accounts, should there be a host record with `TenantId = NULL` for this user? That is the part I'm unsure about — see below.)

## The problem
When `userA` tries to log in, I get **"Invalid username or password!"** (`invalid_grant`).

From the server logs, the token request resolves the tenant to **Host**, then searches for the user
in the **host** context and finds nothing:

```xml
Starting resolving tenant...
Trying to resolve tenant through 'CurrentUser'...
Trying to resolve tenant through 'AbpAccount'...
Tenant resolved by 'AbpAccount' as 'Host'.
No tenant resolved.
...
No user found matching username: "userA@example.com"
→ invalid_grant: "Invalid username or password!"

Notice my custom header resolver (linksetenantid) is never reached — the AbpAccount contributor resolves to Host and the chain stops.

I send the login request like this:

POST /connect/token
linksetenantid: <Dev tenant id>          <-- my custom tenant header
grant_type=password
username=userA@example.com
password=<password>
client_id=...&client_secret=...&scope=...

…but the linksetenantid header appears to be ignored at the token endpoint.

My questions

  1. Why can userA not log in after being invited+accepted under Shared accounts?
  2. Is the host record supposed to have TenantId = NULL? In my host DB the user only appears with a tenant id, and there is no TenantId = NULL host record. Is the accept flow supposed to create a host (TenantId = NULL) record, and could the missing host record be why login can't find the user in host context?
  3. How is a Shared user supposed to log in to a tenant? It looks like the linksetenantid header is intentionally ignored at /connect/token once Shared is enabled (the AbpAccount tenant resolver forces Host). Is the intended flow: log in host-side (no tenant), then use the SwitchTenant grant? If so, host login requires a host (TenantId = NULL) record — which I don't seem to have for this user.
  4. Does database-per-tenant change any of this? Since host and tenant are separate databases, does the invite/accept flow correctly create the host record in the host DB and the tenant copy in the tenant DB? Could a cross-database issue cause the host record to be missing?
  5. Is the custom tenant header (TenantKey = "linksetenantid") supported with Shared accounts, or does Shared mode change tenant resolution at login such that the header is not used?

Exception message and full stack trace

(login response)
{
  "error": "invalid_grant",
  "error_description": "Invalid username or password!",
  "error_uri": "https://documentation.openiddict.com/errors/ID2024"
}

(No server-side exception — the request completes; the log shows "No user found matching username".)

Summary of what I think is happening

After enabling Shared accounts, login resolves to Host (the linksetenantid header is not used at the token endpoint), and the user appears to have no host (TenantId = NULL) record — only records with a tenant id — so host-side login can't find the user. I'd like to know the correct way to make an invited+accepted Shared user able to log in, and whether the missing TenantId = NULL host record (or the custom tenant header being ignored at login) is the root cause.

I just started using session management with openiddict, now everything looks ok. but when I enabled LogoutFromSameTypeDevices I find that all session has same Device on DB (AbpSessions) so I wonder why and how to control what value is passed to db. second question is it possible to make custom rule about it? for example if client is Web only 1 login allowed, if client is "Mobile" you can 5 or unlimited token etc. ?

abp version 9.2.0

Yes I enabled it. I shared my host module with link because its too long. https://justpaste.it/hvxmf

Hello,

We have recently upgraded our ABP modules from version 7.0.3 to 9.1.0, and we are looking to enable and utilize Session Management in our solution.

Our project currently references the following modules:

Volo.Abp.Identity.Pro.*

Volo.Abp.IdentityServer.*

After the upgrade, we confirmed that the AbpSessions table has been created successfully. However, we are seeing logs indicating that a SessionId is not found in the database (it means never saved). It's unclear why this is happening, and we would appreciate any guidance on this.

Could you please advise on what steps we should take to correctly enable and use the session management feature?

Thank you in advance for your support.

Showing 1 to 6 of 6 entries
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 October 05, 2026, 11:32
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.