18 Answer(s)
-
0
The invitation email body is rendered correctly, but your application’s public URL (host/scheme) is not being included while generating the invitation link.
That’s why you only see a relative URL like:[/Account/InviteUser?token=...]and many email clients won’t make it clickable / it won’t point to your site.Fix: configure the application URL(s) so ABP can generate an absolute invite link (e.g.
https://yourdomain.com/Account/InviteUser?...) in the email.In your
*.HttpApi.Host(orWebproject, depending on your UI), configureAppSelfUrl(and optionallyRedirectAllowedUrls) inappsettings.json:
{ "App": { "SelfUrl": "https://yourdomain.com", "RedirectAllowedUrls": "https://yourdomain.com" } }If you are running behind a reverse proxy / load balancer, also make sure forwarded headers are correctly configured so the request scheme/host are detected as external (
https, correct host). Otherwise ABP may still generate non-public URLs.After setting
App:SelfUrlproperly, re-send the invitation: the email should contain a full absolute link and display as a clickable “Accept invitation” link.- https://abp.io/docs/latest/modules/account/shared-user-accounts
- https://abp.io/docs/10.2/modules/account/shared-user-accounts
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) -
0
Hi thanhvl1,
The link is a relative path because
Applications["MVC"].RootUrlisn't set when the invite email is generated. Two cases:Non-tiered solution (
*.Web/*.Blazor.Server/*.Blazor.WebApp/*.HttpApi.HostWithIds) — the template already wiresApp:SelfUrltoApplications["MVC"].RootUrl. Just make sureApp:SelfUrlinappsettings.jsonis a full URL with scheme + host, e.g.https://your-app.example.com.Tiered solution (separate
*.HttpApi.Host+*.AuthServer) — theInviteUsersAPI runs in*.HttpApi.Host, but itsConfigureUrlsdoesn't registerApplications["MVC"].RootUrl. Add this one line in*.HttpApi.Host/*HttpApiHostModule.cs:Configure<AppUrlOptions>(options => { options.Applications["MVC"].RootUrl = configuration["AuthServer:Authority"]; // ...existing Angular registrations });The
Account/InviteUserpage is served by the AuthServer in a tiered solution, which is why we point toAuthServer:Authority. After this, the email link becomes absolute likehttps://your-authserver.example.com/Account/InviteUser?token=...and clickable.We'll fix this in the project template in the next ABP version.
Thanks!
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
-
0
Hi thanhvl1,
The Switch Tenant modal lists all tenant associations the current user has on the Host side. In Shared User Accounts mode,
AbpUserson the Host database uses three values forTenantId:Guid.Empty— the user's global root record (filtered out)NULL— Host association (rendered as "Host" in the modal)<tenant guid>— a tenant the user has joined
If the user has a row with
TenantId IS NULL, the modal shows a "Host" entry that lets you switch back to Host from any tenant. The fact that "Host" doesn't appear in your screenshot suggests this user has no Host association — they were only invited intofti/tff.To help me confirm, could you share a few details:
- Is it the same user account signed in for both screenshots, or two different accounts? (The Host-side screenshot might be your Host admin, while the tenant-side one might be a different account that was only invited into
fti/tff.) - Which ABP version are you on (10.2 / 10.3 / 10.4)?
- Could you run this on the Host database for the email you're testing with, and paste the result?
SELECT Id, UserName, Email, TenantId FROM AbpUsers WHERE Email = '<user email here>';Most likely outcome — if there's no row with
TenantId IS NULL, a Host admin can invite that user to Host through the standard invitation flow (open the invite dialog while the current tenant is Host). After acceptance, the Host association is created and "Host" will appear in the Switch Tenant modal.Thanks!
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi thanhvl1,
One more question — the Switch Tenant modal in your screenshots looks like it might not be our default one. Did you customize the modal, or are you using a community / third-party UI?
If yes, could you share the relevant code (or at least which API it calls to load the tenant list)? That'll help us trace where the displayed list comes from.
Thanks!
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
In this admin app is a standard Angular host: it uses
@volo/abp.ng.accountandprovideAccountPublicConfig()inapp.config.ts, plus Lepton X (@volosoft/abp.ng.theme.lepton-x). We do not ship a custom “Switch tenant” dialog implementation in our admin src/ tree.I also have checked db and api
But there this is weird because when I logged in it's come to next screen choose tenants, in this UI, I see the Host
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi thanhvl1,
To narrow this down, could you check a few things after switching to
fti?Decode the access token. Open DevTools (F12) → Network → pick any authenticated API request after the switch → copy the
Authorization: Bearer <token>value → paste it at https://jwt.io. What does thetenant_id(ortenantid) claim show?Database setup. Are
fti/tffconfigured as shared database or database-per-tenant?(If per-tenant) Run this on the
ftitenant database (not Host):
SELECT Id, Email, TenantId FROM AbpUsers WHERE Email = 'thanhvl1@aaa.com';Once we have these, we can pinpoint where the list is coming from.
Thanks!
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
I've used shared database
After choosing tenant fti then get the token to decode and I get
"tenantid": "3a1af4d8-364d-86dc-87c9-66b8c5f291f5"match this api{ "items": [ { "tenantId": "3a1aff20-3e15-936d-eed4-9ae0c7235e8e", "tenantName": "tff", "email": "thanhvl1@aaa.com" }, { "tenantId": "3a1af4d8-364d-86dc-87c9-66b8c5f291f5", "tenantName": "fti", "email": "thanhvl1@aaa.com" } ] }Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi thanhvl1,
We tried to reproduce this on our side with the same setup (Tiered Angular Pro, Shared User Accounts, shared database, the same 3 user rows in
AbpUsers), but we couldn't reproduce the issue — switching toftiand callingGET /api/account/user-sharingwith the new token returns[Host, tff]as expected, withHostincluded.To narrow this down, could you share a few more pieces of info?
Authorizationheader from actual XHR requests after switching tofti. Open DevTools → Network → after the switch, pick 2–3 XHR requests (for example/api/abp/application-configurationand the modal's/api/account/user-sharing) → copy each request's fullAuthorization: Bearer ...value → decode each at https://jwt.io. We want to confirm whether every XHR is sending the sameftitoken you decoded earlier, or whether some are still carrying the oldHosttoken.Backend debug log around the
/api/account/user-sharingrequest. In your AuthServer'sappsettings.json, lower theSerilog/logging level forVolo.Abp.MultiTenancy(or the root logger) toDebug, restart AuthServer, reproduce once, then share the log lines around the request. The single most useful line looks like:
[DBG] Tenant resolved by 'CurrentUser' as '<value>'.That tells us exactly which tenant the server saw when handling the modal's request.
Exact ABP version (e.g. 10.2.1, 10.3.2, 10.4.0) — we want to make sure we're comparing the same code.
How are you switching to
fti? Through the Switch Tenant modal (clickingftiin the actions menu), through the login page with a tenant selection, through a URL parameter, or some other flow?Have you customized any of these on the server side?
UserSharingAppService,IdentityUserManager,IUserClaimsPrincipalFactory, or any tenant-resolution / authentication / HTTP interceptor pieces. Likewise on the Angular side — any custom interceptor or override of@volo/abp.commercial.ng.ui/configSwitchTenantService/RestService.Hard refresh test — after switching to
fti, hit Ctrl+Shift+R (Cmd+Shift+R on Mac) to do a hard reload, then open the Switch Tenant modal again. DoesHostshow up now? This rules out browser/SPA caching as the cause.
Thanks!
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
checking 2-3 XHR the token Bear value is almost the same, it's from
ftiafter switching tenant. The weird thing I've checked is that in localhost, I can still see Host as listed in the modal of switching tenantI've got this issue when deploying to AKS, so is that maybe caused by a behind proxy ?
For more info, I use ABP version 10.3.0 And I got through the Switch Tenant modal in Angular admin I also try hard refresh test but still the same
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
-
0
Hi thanhvl1,
Great comparison — Angular vs Postman returning different responses with the same token strongly points to Angular sending something extra in the request that's changing the server's behavior.
Could you share two things?
The full Request Headers of the Angular call to
/api/account/user-sharingfrom DevTools Network — please include all headers (not justAuthorization), especially anyCookie,Origin,Referer, or__tenant-style headers. A screenshot of the fully expanded Request Headers section works.How are the Angular app and AuthServer exposed on AKS? Same domain (e.g.
app.example.comandapp.example.com/authserver) or different domains (e.g.app.example.comandauthserver.example.com)? If they share a domain, the browser may be sending the AuthServer's session cookie automatically along with the Bearer token, which could change how the server authenticates the request.
Once we see the headers and the deployment layout, we can pinpoint and fix it.
Thanks!
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
-
0
Hi thanhvl1,
Excellent debugging — that nails it down to the
Cookieheader.To confirm exactly what's happening on the server side, could you share the full AuthServer Debug log for one reproduction of the
/api/account/user-sharingcall?To enable Debug-level logs, please follow this guide: https://abp.io/support/questions/8622/How-to-enable-Debug-logs-for-troubleshoot-problems
Then restart AuthServer, sign in, switch to a tenant, open the Switch Tenant modal once, and attach the complete log (please don't trim it).
Thanks!
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
do you need me to reproduce this and send you my log for further document ?
Because when I try to implement this
middlewarenow it can bypass and the switch tenant now show as expected. But I don't know if it will cause other errorpublic sealed class StripCookiesForBearerAccountApiMiddleware : IMiddleware { private const string AccountPublicApiPrefix = "/api/account"; public async Task InvokeAsync(HttpContext context, RequestDelegate next) { var path = context.Request.Path; if (!path.StartsWithSegments(AccountPublicApiPrefix, StringComparison.OrdinalIgnoreCase)) { await next(context); return; } var authHeader = context.Request.Headers.Authorization.ToString(); if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { await next(context); return; } context.Request.Headers.Remove("Cookie"); await next(context); } }Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
hi
Is your problem solved? If not, you can continue sharing the logs. https://abp.io/support/questions/10659/Shared-User-Accounts---sent-email-format#answer-3a213270-a9ea-58ca-2111-9b161116c3a1
Because when I try to implement this middleware now it can bypass and the switch tenant now show as expected. But I don't know if it will cause other error
You can remove your middleware to troubleshoot the problem.
Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)











