Provide us with the following info:
🧐 Hint: If you are using the ABP Studio, you can see all the information about your solution from the configuration window, which opens when you right-click on the solution and click on the Solution Configuration
button.
- Template: microservice
- Created ABP Studio Version: 1.0.2
- Current ABP Studio Version: 1.2.2
- Multi-Tenancy: Yes
- UI Framework: mvc
- Theme: leptonx
- Theme Style: system
- Theme Menu Placement: side
- Run Install Libs: Yes
- Database Provider: ef
- Database Management System: sqlserver
- Mobile Framework: none
- Public Website: No
- Social Login: Yes
- Include Tests: Yes
- Dynamic Localization: Yes
- Kubernetes Configuration: Yes
- Grafana Dashboard: Yes
- Use Local References: No
- Optional Modules:
- GDPR
- TextTemplateManagement
- AuditLogging
- OpenIddictAdmin
- Selected Languages: English, English (United Kingdom), Español
- Default Language: English
- Create Command: abp new CloverleafCMS -t microservice --ui-framework mvc --database-provider ef --database-management-system sqlserver --theme leptonx --skip-migrator --without-cms-kit --dont-run-bundling -no-file-management -no-language-management
We're using the microservice template and using the auth-server app for authentication. The auth-server application has been slightly modified for custom branding. We are running the authserver and microservices in Azure Kubernetes.
- Exception message and full stack trace: The scenerio is we have a multi-tenant app and we are testing the confirm email and reset password processes and are getting the error below when we change to a new tenant. We are deriving the tenant from the url in the form of 'auth.tenantname.cloverleafcms.us'. Our tenant resolver interrogates the httpContext host and resolves the tenant and sets the context.TenantIdOrName property. We are getting a 404 Page not found (no entity for IdentityUser) error after we register a new user then confirm email'.
- Here is the generated email confirmation link. https://authserver.cloverleafcms.us/Account/EmailConfirmation?userId=74db3c8b-0643-c4bb-706b-3a1c1eaea16a&__tenant=49400f5b-7343-44e6-e5ba-3a1b7de4e30d&confirmationToken=CfDJ8OBio5EftblJhCOdU3MbuvSyv42wt9b1EHmD76kl5OdPV8vUVcLm6sT5vPwp83D%2FNXDBwd9xx55CZwfM7MYFySK4k%2FiOdXL3OSwpoRyr68Woe45GrToIPJyUUjYqOJE6TvD2zpkN%2Bj9ANAidT2eR1fUyMBHVZjbEjzfTVKNtOy94TofV9ocCWo0omRrCE%2Fad6wA%2BtGpg%2F2AFOXmuc2K6unD73DkXnmaWsWxuYAYgJLqdSdFWFVNgDIY%2BZOfcSJQV2A%3D%3D&returnUrl=%2fconnect%2fauthorize%3fclient_id%3dCloverleafCMS%26redirect_uri%3dhttps%253A%252F%252Fhomefree.cloverleafcms.us%252Fsignin-oidc%26response_type%3dcode%26scope%3daddress%2520email%2520phone%2520profile%2520roles%2520ActionItemService%2520AdministrationService%2520AuditLoggingService%2520AuthServer%2520ClientService%2520ClientServicesQuery%2520CommunicationsTemplateService%2520ContactService%2520DocTemplateService%2520DocumentService%2520EngagementLogService%2520FinancialService%2520GdprService%2520GuardianshipService%2520HousingService%2520HudService%2520IdentityService%2520LanguageService%2520MemberConfigService%2520NoteService%2520SaasService%2520ServicesService%2520SMSService%2520StaffService%2520TokenService%2520WorkshopService%26state%3d0576740b185d48c4ad27f739780bfa08%26code_challenge%3dY-HV3IxF-wsSV8jwJs6PZRiDbE_VUam3vD6BaoGc8Sg%26code_challenge_method%3dS256%26response_mode%3dquery
- It has the base address of 'authserver.cloverleafcms.us'. Where in the code does the email confirmation link get generated? We believe that the generated base url needs to be in this format that our HostTenantResolveContributor method is expecting. For instance, 'auth.homefree.cloverleafcms.us/Accoun/EmailConfirmation?...' We will need to add code to format the email confirmation base url and would like guidance on where we need to place the code (module, routine, etc.)
- We are going to need to do the same for the Password Reset generated link too.
Here is the 'HostTenantResolveContributor.cs'
public class HostTenantResolveContributor : TenantResolveContributorBase
{
public override async Task ResolveAsync(ITenantResolveContext context)
{
var currentContextAccessor = context.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
var memberConfigService = context.ServiceProvider.GetRequiredService<IMemberConfigService>();
if (memberConfigService != null)
{
#if DEBUG
currentContextAccessor.HttpContext.Request.Host = new HostString("auth.homefree.cloverleafcms.us"); // Set the host header to a default value for testing purposes
//currentContextAccessor.HttpContext.Request.Path = new PathString("/Account/Login"); // Set the path to root for testing purposes
#endif
string? prefix = currentContextAccessor?.HttpContext?.Request?.GetPrefixFromHost();
Console.WriteLine($"Tenant prefix after GetPrefixFromHost is: {prefix}.");
if (!string.IsNullOrEmpty(prefix) && prefix != "admin")
{
var responseDto = await memberConfigService.GetMemberConfigByUrlPrefixAsync(prefix);
if (responseDto != null && responseDto.Success)
{
var member = responseDto.MemberConfig;
context.TenantIdOrName = member?.Id.ToString();
Console.WriteLine($"Member found for prefix: {prefix}");
Console.WriteLine($"MemberId: {member?.Id.ToString()}");
}
else
{
Console.WriteLine($"Member not found for prefix: {prefix}. See details: {responseDto?.ErrorResponse?.Error?.Message}");
context.TenantIdOrName = null;
}
return;
}
else
{
Console.WriteLine("Tenant prefix not found in the host.");
context.TenantIdOrName = null;
}
}
else
{
Console.WriteLine("MemberConfigService is not available.");
context.TenantIdOrName = null;
}
}
public override string Name => "Host";
}
Here is the GetPrefixFromHost.cs extension
public static class HttpRequestExtensions
{
public static string? GetPrefixFromHost(this HttpRequest request)
{
if (request?.Host.HasValue != true)
{
return "invalid";
}
string host = request.Host.Host;
try
{
// Check for localhost
if (host.Contains("localhost"))
return null;
if (host.Contains("host.docker.internal"))
return null;
// Split the host into parts
var parts = host.Split('.');
if (parts.Length < 2)
return null;
var first = parts[0];
var second = parts[1];
if (second.Equals("admin", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("Admin subdomain detected, returning null for prefix.");
return null;
}
if (first.Equals("auth", StringComparison.OrdinalIgnoreCase) && parts.Length >= 3)
{
// Return the second part as prefix
Console.WriteLine($"Member subdomain detected, returning {parts[1]} for prefix.");
return parts[1];
}
return "invalid"; // Return "invalid" for other cases
}
catch
{
// Return "invalid" for invalid URLs
return "invalid";
}
}
}
2 Answer(s)
-
0
The issue you are experiencing is due to the email confirmation and reset password links being generated with the wrong base URL (e.g., https://authserver.cloverleafcms.us instead of the required tenant-specific subdomain like https://auth.homefree.cloverleafcms.us). This causes the tenant resolver to fail, resulting in a 404 error because the user cannot be found in the wrong tenant context.
To resolve this, you need to ensure that the URLs generated for email confirmation and password reset are tenant-aware and use the correct subdomain format that your HostTenantResolveContributor expects.
Solution
Configure AppUrlOptions for Tenant-Aware URLs
In your AuthServer module (typically in
AuthServerHostModule
), configure theAppUrlOptions
to generate tenant-specific URLs for account actions:
Configure<AppUrlOptions>(options => { options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"]; options.Applications["MVC"].Urls[AccountUrlNames.EmailConfirmation] = "Account/EmailConfirmation"; options.Applications["MVC"].Urls[AccountUrlNames.PasswordReset] = "Account/ResetPassword"; });
Set the Correct RootUrl Pattern
In your
appsettings.json
for the AuthServer, set theSelfUrl
to use a pattern that includes the tenant prefix, for example:
"App": { "SelfUrl": "https://auth.{0}.cloverleafcms.us" }
This allows ABP to replace
{0}
with the actual tenant name when generating URLs.Update Email Link Generation
ABP uses the
AppUrlProvider
to generate URLs for email confirmation and password reset. By configuring theSelfUrl
as above, the generated links will use the correct tenant subdomain.Ensure Tenant Resolution from Subdomain
Your
HostTenantResolveContributor
andGetPrefixFromHost
logic should already extract the tenant from the subdomain (e.g.,auth.homefree.cloverleafcms.us
→homefree
). Make sure this logic is consistent and that the generated links match the expected format.Database Client Configuration
If you are using IdentityServer, ensure that the
RootUrl
for your client in the database also uses the{0}
pattern:
"RootUrl": "https://auth.{0}.cloverleafcms.us"
This ensures that redirect URIs and CORS origins are tenant-aware.
Summary
- Set
"App:SelfUrl": "https://auth.{0}.cloverleafcms.us"
in your AuthServer's configuration. - Configure
AppUrlOptions
for account actions. - Ensure your email templates use the
AppUrlProvider
to generate links. - Confirm your tenant resolver and subdomain logic are consistent.
This will ensure that email confirmation and password reset links are generated with the correct tenant-specific subdomain, allowing your tenant resolver to work and preventing 404 errors.
This approach aligns with common ABP conventions for multi-tenant subdomain setups in microservice solutions.
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
hi
The URL contains the
__tenant
info.https://authserver.cloverleafcms.us/Account/EmailConfirmation?userId=74db3c8b-0643-c4bb-706b-3a1c1eaea16a&__tenant=49400f5b-7343-44e6-e5ba-3a1b7de4e30d&
The
Volo.Abp.Account.Emailing.AccountEmailer
service is used to generate and send the mail.appUrlProvider.GetUrlAsync(appName, AccountUrlNames.EmailConfirmation)
https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs#L15
You can try to override the
Volo.Abp.Account.Public.Web.Pages.Account.EmailConfirmationModel
to check the current tenant info.Thanks.