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
2 Answer(s)
-
0
Hi @dominici
Your assumption about the tenant filter is correct: if the activation request is executed in the host context,
UserManager.FindByIdAsyncwill not find a tenant user.However, disabling the
MayHaveTenantfilter inActivateEmailis not the recommended fix. The built-in email activation flow is designed to runActivateEmailunder the correct tenant context.In the standard ASP.NET Zero flow:
- The activation URL includes tenant information.
- Query parameters are encrypted into the
cparameter. - Angular/React calls
ResolveTenantId(c)beforeActivateEmail. - MVC calls
SwitchToTenantIfNeeded(input.TenantId)beforeActivateEmail. - Then
ActivateEmailruns 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
cparameter. - If you customized the email template or activation page, ensure the
cparameter is not removed or changed. - If you use a custom frontend/mobile client, first call
ResolveTenantIdwith thecvalue, set the tenant cookie/header, then callActivateEmail. - If you call the API manually, send the tenant id with the request, for example using the
Abp.TenantIdheader/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 supportedCopy & paste or drag & drop images (max 30 MB per image) -
0
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 supportedCopy & paste or drag & drop images (max 30 MB per image)