Hi,
We have need to define multiple different ABP based solutions and yet still use the same openiddict auth server for a single sign on experience and central management of users, roles and permissions. The auth server has been configured and the client applications including the other client api's (ie. the host api's of the various modules) have been registered. The redirect works and a valid token is issued, however the authentication and authorization fails in the host api. We are able to get this working, if we manually create the tenant, the user and the session record in the database that is used by the host api. Can we programmatically create the tenant if it doesn't exist, create the user if it doesn't exist, and the session context for that user so that ICurrentUser and ICurrentTenant works? Alternatively is there a different approach we can take to take to implement this?
ABP Version - 9.2.0
7 Answer(s)
-
0
hi
however the authentication and authorization fails in the host api.
Do you have any error logs? Why do you think creating a tenant will fix it?
https://abp.io/support/questions/8622/How-to-enable-Debug-logs-for-troubleshoot-problems
Thanks.
-
0
[maliming] said: hi
however the authentication and authorization fails in the host api.
Do you have any error logs? Why do you think creating a tenant will fix it?
https://abp.io/support/questions/8622/How-to-enable-Debug-logs-for-troubleshoot-problems
Thanks.
Hi,
Here is an example of the error: Volo.Abp.Domain.Entities.EntityNotFoundException: There is no such an entity. Entity type: Volo.Abp.Identity.IdentityUser, id: 3a1b19ed-1b99-0633-bfaf-f2bbd8fa0af3 at Volo.Abp.Identity.IdentityUserManager.GetByIdAsync(Guid id) at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo) at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue
1.ProceedAsync() at Volo.Abp.Uow.UnitOfWorkInterceptor.InterceptAsync(IAbpMethodInvocation invocation) at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter
1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func3 proceed) at Volo.Abp.Identity.IdentityDynamicClaimsPrincipalContributorCache.<>c__DisplayClass23_0.<<GetAsync>b__0>d.MoveNext() --- End of stack trace from previous location --- at Volo.Abp.Caching.DistributedCache
2.GetOrAddAsync(TCacheKey key, Func1 factory, Func
1 optionsFactory, Nullable1 hideErrors, Boolean considerUow, CancellationToken token) at Volo.Abp.Identity.IdentityDynamicClaimsPrincipalContributorCache.GetAsync(Guid userId, Nullable
1 tenantId) at Volo.Abp.Identity.IdentityDynamicClaimsPrincipalContributor.ContributeAsync(AbpClaimsPrincipalContributorContext context)If we then manually create the user, then it works on host but then fails on tenant untill we manually create the tenant. The different ABP solutions use completely separate databases.
-
0
To achieve seamless single sign-on (SSO) across multiple ABP-based solutions using a shared OpenIddict auth server, you must ensure that tenants and users are synchronized across all client applications. ABP does not provide built-in automatic tenant/user provisioning out-of-the-box for this scenario, but you can implement the following approach:
Programmatic Tenant and User Creation:
- Implement logic in your client applications (host APIs) to detect when an authenticated user or tenant does not exist in the local database.
- When a user logs in via OpenIddict and is authenticated, check if the corresponding tenant and user exist in your application's database.
- If not, programmatically create the tenant and user using the appropriate ABP services (
ITenantAppService
,IIdentityUserAppService
). - This can be done in a custom authentication event handler or middleware that runs after token validation.
Example (simplified):
public class CustomOpenIddictEvents : OpenIddictServerEvents.Authentication { public override async Task HandleAsync(AuthenticationContext context) { var tenantService = context.HttpContext.RequestServices.GetRequiredService<ITenantAppService>(); var userService = context.HttpContext.RequestServices.GetRequiredService<IIdentityUserAppService>(); var tenantName = /* extract from claims or token */; var userName = /* extract from claims or token */; // Check and create tenant if needed var tenant = await tenantService.FindByNameAsync(tenantName); if (tenant == null) { await tenantService.CreateAsync(new TenantCreateDto { Name = tenantName }); } // Check and create user if needed var user = await userService.FindByUserNameAsync(userName); if (user == null) { await userService.CreateAsync(new IdentityUserCreateDto { UserName = userName, ... }); } } }
Session Context for ICurrentUser and ICurrentTenant:
- Ensure that after provisioning, the correct tenant and user context is set for the current request.
- ABP resolves
ICurrentUser
andICurrentTenant
based on the claims in the access token and the tenant resolution strategy (domain, header, etc.). - If your token contains the correct
tenantid
and user claims, and your tenant/user exists in the database, ABP will resolve the context automatically.
Alternative Approach: Centralized User/Tenant Store:
- Instead of duplicating tenants and users in each solution, consider using a shared database for user and tenant management across all ABP solutions.
- Configure all client applications to use the same connection string for the identity and tenant tables.
- This ensures that user and tenant data is always in sync, and SSO works seamlessly.
Best Practice:
- The recommended approach is to use a shared identity/tenant store if possible.
- If you must keep separate databases, implement the programmatic provisioning logic as described above.
No official ABP documentation provides a ready-made solution for automatic cross-solution tenant/user provisioning, but the above approach aligns with ABP's extensibility model and common practices.
This approach aligns with common ABP conventions, but specific official documentation for this exact scenario is not available.
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.
-
0
Does the tenant and the user absolutely need to exist? The example does not compile. Is there either more docs or other support tickets or examples that we can refer to?
-
0
hi
Volo.Abp.Domain.Entities.EntityNotFoundException: There is no such an entity. Entity type: Volo.Abp.Identity.IdentityUser, id: 3a1b19ed-1b99-0633-bfaf-f2bbd8fa0af3 at Volo.Abp.Identity.IdentityUserManager.GetByIdAsync(Guid id)
Why does this user not exist?
Is your application using the same database as the auth server(openiddit)?
Thanks.
-
0
[maliming] said: hi
Volo.Abp.Domain.Entities.EntityNotFoundException: There is no such an entity. Entity type: Volo.Abp.Identity.IdentityUser, id: 3a1b19ed-1b99-0633-bfaf-f2bbd8fa0af3 at Volo.Abp.Identity.IdentityUserManager.GetByIdAsync(Guid id)
Why does this user not exist?
Is your application using the same database as the auth server(openiddit)?
Thanks.
Hi, No separate databases.
-
0
hi
Your
host api
depends on the Identity module.They should use the
Identity
database.{ "ConnectionStrings": { "Default": "Server=localhost;", "AbpIdentity": "Server=localhost;" } }
You can also remove the Identity module from your
host api
Thanks.