Base solution for your next web application
Open Closed

Email confirm strange behaviour #12671


User avatar
0
dominici created

Hi, I'm trying to use the built-in account activation by email.

My application is multi-tenant, so multi-tenant management is active.

This is the original code of the ActivateEmail method:

public async Task ActivateEmail(ActivateEmailInput input)
    {
        var user = await UserManager.FindByIdAsync(input.UserId.ToString());
        if (user != null && user.IsEmailConfirmed)
        {
            return;
        }

        if (user == null || user.EmailConfirmationCode.IsNullOrEmpty() ||
            user.EmailConfirmationCode != input.ConfirmationCode)
        {
            throw new UserFriendlyException(L("InvalidEmailConfirmationCode"),
                L("InvalidEmailConfirmationCode_Detail"));
        }

        user.IsEmailConfirmed = true;
        user.EmailConfirmationCode = null;

        await UserManager.UpdateAsync(user);
}

But for me doesn't work. I'm trying to register user to Tenant with id 3 with a standard registration form. I'm using the provided "public async Task<RegisterOutput> Register(RegisterInput input)" method. The email is sended but when I ckick on it, the user is not found and the activation fail. I think it's due to the automatic tenant filter on uow. So, the code is trying to find the user by id and by TenantId == null.

To make it works, I need to change the code as follow:

[UnitOfWork(IsDisabled =true)]
    public async Task ActivateEmail(ActivateEmailInput input)
    {

        using var uow = UnitOfWorkManager.Begin();
        using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant))
        {
            var user = await UserManager.FindByIdAsync(input.UserId.ToString());
            if (user != null && user.IsEmailConfirmed)
            {
                return;
            }

            if (user == null || user.EmailConfirmationCode.IsNullOrEmpty() ||
                user.EmailConfirmationCode != input.ConfirmationCode)
            {
                throw new UserFriendlyException(L("InvalidEmailConfirmationCode"),
                    L("InvalidEmailConfirmationCode_Detail"));
            }

            user.IsEmailConfirmed = true;
            user.EmailConfirmationCode = null;

            await UserManager.UpdateAsync(user);
        }
    }

So my question are:

  • I'm missing somethig?
  • Is the email activation feature provided with the framework not working?

Thanks in advance

Br, Fabrizio

Markdown is supported
Copy & paste or drag & drop images (max 30 MB per image)

2 Answer(s)
  • User Avatar
    0
    oguzhanagir created
    Support Team

    Hi @dominici

    Your assumption about the tenant filter is correct: if the activation request is executed in the host context, UserManager.FindByIdAsync will not find a tenant user.

    However, disabling the MayHaveTenant filter in ActivateEmail is not the recommended fix. The built-in email activation flow is designed to run ActivateEmail under the correct tenant context.

    In the standard ASP.NET Zero flow:

    • The activation URL includes tenant information.
    • Query parameters are encrypted into the c parameter.
    • Angular/React calls ResolveTenantId(c) before ActivateEmail.
    • MVC calls SwitchToTenantIfNeeded(input.TenantId) before ActivateEmail.
    • Then ActivateEmail runs with the correct tenant filter.

    So if the user is not found, most likely the activation request is being sent without setting the tenant context first.

    Please check these points:

    • Make sure the activation link contains the original encrypted c parameter.
    • If you customized the email template or activation page, ensure the c parameter is not removed or changed.
    • If you use a custom frontend/mobile client, first call ResolveTenantId with the c value, set the tenant cookie/header, then call ActivateEmail.
    • If you call the API manually, send the tenant id with the request, for example using the Abp.TenantId header/cookie.

    We do not recommend disabling tenant filters for this method, because it bypasses tenant isolation. The correct approach is to switch to the tenant from the activation link before calling ActivateEmail.

    Markdown is supported
    Copy & paste or drag & drop images (max 30 MB per image)
  • User Avatar
    0
    dominici created

    Hi oguzhanagir, thanks for the explanation. I have a custom frontend/mobile and I hadn't noticed the call to ResolveTenantId.

    In my opionion it's a little bit tricky and I prefer to do also a tenant check server side and, if it's ok, I can set automatically the tenant context.

    Anyway, I'm going to fix it.

    Br, Fabrizio

    Markdown is supported
    Copy & paste or drag & drop images (max 30 MB per image)