Open Closed

Email Confirmation and Reset Password links (from email) redirects to page with 404 #9843


User avatar
0
fordz created

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.

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)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent

    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

    1. Configure AppUrlOptions for Tenant-Aware URLs

      In your AuthServer module (typically in AuthServerHostModule), configure the AppUrlOptions 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";
       });
    
    1. Set the Correct RootUrl Pattern

      In your appsettings.json for the AuthServer, set the SelfUrl 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.

    1. Update Email Link Generation

      ABP uses the AppUrlProvider to generate URLs for email confirmation and password reset. By configuring the SelfUrl as above, the generated links will use the correct tenant subdomain.

    2. Ensure Tenant Resolution from Subdomain

      Your HostTenantResolveContributor and GetPrefixFromHost logic should already extract the tenant from the subdomain (e.g., auth.homefree.cloverleafcms.ushomefree). Make sure this logic is consistent and that the generated links match the expected format.

    3. 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.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    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.

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.0.0-preview. Updated on September 01, 2025, 08:37