Sign In Without Specifying Tenant in ASP.NET Zero Mvc
Normally, ASP.NET Zero uses tenant information in login transactions. This document shows you how to implement tenant information independent login.
Important Note: Your user's email addresses have to be unique to implement this solution. Otherwise, this solution may not work correctly.
Updating LogInManager
First of all, open
*.Application\Authorization\LogInManager
.Add lines shown below
UserStore _userStore
public LogInManager(
//....
UserStore userStore
){
_userStore = userStore;
}
[UnitOfWork]
public async Task<AbpLoginResult<Tenant, User>> LoginAsync(UserLoginInfo login)
{
var result = await LoginAsyncInternal(login);
await SaveLoginAttemptAsync(result, result.Tenant.Name, login.ProviderKey + "@" + login.LoginProvider);
return result;
}
protected async Task<AbpLoginResult<Tenant, User>> LoginAsyncInternal(UserLoginInfo login)
{
if (login == null || login.LoginProvider.IsNullOrEmpty() || login.ProviderKey.IsNullOrEmpty())
{
throw new ArgumentException("login");
}
using (UnitOfWorkManager.Current.DisableFilter(AbpDataFilters.MayHaveTenant))
{
var user = await _userStore.FindAsync(login);
if (user == null)
{
return new AbpLoginResult<Tenant, User>(AbpLoginResultType.UnknownExternalLogin);
}
//Get and check tenant
Tenant tenant = null;
if (!MultiTenancyConfig.IsEnabled)
{
tenant = await GetDefaultTenantAsync();
}
else if (user.TenantId.HasValue)
{
tenant = await TenantRepository.FirstOrDefaultAsync(t => t.Id == user.TenantId);
if (tenant == null)
{
return new AbpLoginResult<Tenant, User>(AbpLoginResultType.InvalidTenancyName);
}
if (!tenant.IsActive)
{
return new AbpLoginResult<Tenant, User>(AbpLoginResultType.TenantIsNotActive, tenant);
}
}
return await CreateLoginResultAsync(user, tenant);
}
}
Then, your LogInManager
will be able to use given user's tenant for login process.
Updating UserManager
Go to
*.Core\Authorization\Users\UserManager
.And add following lines;
public async Task<int?> TryGetTenantIdOfUser(string userEmail)
{
using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.MayHaveTenant))
{
var user = await Users.SingleOrDefaultAsync(u => u.EmailAddress == userEmail.Trim());
return user?.TenantId;
}
}
Updating AccountController
Then, go to
*.Mvc\Controllers\AccountController
.Replace the function named
GetTenancyNameOrNull
with the following content
private async Task<string> GetTenancyNameOrNull(string email)
{
var tenantId = await _userManager.TryGetTenantIdOfUser(email);
if (!tenantId.HasValue)
{
return null;
}
return _tenantCache.GetOrNull(tenantId.Value)?.TenancyName;
}
- Replace the function named
Login(LoginViewModel loginModel, string returnUrl = "", string returnUrlHash = "", string ss = "")
with the following content
[HttpPost]
[UnitOfWork]
public virtual async Task<JsonResult> Login(LoginViewModel loginModel, string returnUrl = "", string returnUrlHash = "", string ss = "")
{
returnUrl = NormalizeReturnUrl(returnUrl);
if (!string.IsNullOrWhiteSpace(returnUrlHash))
{
returnUrl = returnUrl + returnUrlHash;
}
if (UseCaptchaOnLogin())
{
await _recaptchaValidator.ValidateAsync(HttpContext.Request.Form[RecaptchaValidator.RecaptchaResponseKey]);
}
var loginResult = await GetLoginResultAsync(loginModel.UsernameOrEmailAddress, loginModel.Password, await GetTenancyNameOrNull(loginModel.UsernameOrEmailAddress));//use new GetTenancyNameOrNull method that you add previously
if (loginResult?.Tenant?.Id != AbpSession.TenantId)
{
SetTenantIdCookie(loginResult?.Tenant?.Id);
CurrentUnitOfWork.SetTenantId(loginResult?.Tenant?.Id);
}
if (!string.IsNullOrEmpty(ss) && ss.Equals("true", StringComparison.OrdinalIgnoreCase) && loginResult.Result == AbpLoginResultType.Success)
{
loginResult.User.SetSignInToken();
returnUrl = AddSingleSignInParametersToReturnUrl(returnUrl, loginResult.User.SignInToken, loginResult.User.Id, loginResult.User.TenantId);
}
if (_settingManager.GetSettingValue<bool>(AppSettings.UserManagement.AllowOneConcurrentLoginPerUser))
{
await _userManager.UpdateSecurityStampAsync(loginResult.User);
}
if (loginResult.User.ShouldChangePasswordOnNextLogin)
{
loginResult.User.SetNewPasswordResetCode();
return Json(new AjaxResponse
{
TargetUrl = Url.Action(
"ResetPassword",
new ResetPasswordViewModel
{
TenantId = AbpSession.TenantId,
UserId = loginResult.User.Id,
ResetCode = loginResult.User.PasswordResetCode,
ReturnUrl = returnUrl,
SingleSignIn = ss
})
});
}
var signInResult = await _signInManager.SignInOrTwoFactorAsync(loginResult, loginModel.RememberMe);
if (signInResult.RequiresTwoFactor)
{
return Json(new AjaxResponse
{
TargetUrl = Url.Action(
"SendSecurityCode",
new
{
returnUrl = returnUrl,
rememberMe = loginModel.RememberMe
})
});
}
Debug.Assert(signInResult.Succeeded);
await UnitOfWorkManager.Current.SaveChangesAsync();
return Json(new AjaxResponse { TargetUrl = returnUrl });
}
- Replace the function named
ExternalLoginCallback(string returnUrl, string remoteError = null, string ss = "")
with the following content
[UnitOfWork]
public virtual async Task<ActionResult> ExternalLoginCallback(string returnUrl, string remoteError = null, string ss = "")
{
returnUrl = NormalizeReturnUrl(returnUrl);
if (remoteError != null)
{
Logger.Error("Remote Error in ExternalLoginCallback: " + remoteError);
throw new UserFriendlyException(L("CouldNotCompleteLoginOperation"));
}
var externalLoginInfo = await _signInManager.GetExternalLoginInfoAsync();
if (externalLoginInfo == null)
{
Logger.Warn("Could not get information from external login.");
return RedirectToAction(nameof(Login));
}
var loginResult = await _logInManager.LoginAsync(externalLoginInfo);//use new login method that you add previously
switch (loginResult.Result)
{
case AbpLoginResultType.Success:
{
await _signInManager.SignInAsync(loginResult.Identity, false);
if (!string.IsNullOrEmpty(ss) && ss.Equals("true", StringComparison.OrdinalIgnoreCase) && loginResult.Result == AbpLoginResultType.Success)
{
loginResult.User.SetSignInToken();
returnUrl = AddSingleSignInParametersToReturnUrl(returnUrl, loginResult.User.SignInToken, loginResult.User.Id, loginResult.User.TenantId);
}
return Redirect(returnUrl);
}
case AbpLoginResultType.UnknownExternalLogin:
return await RegisterForExternalLogin(externalLoginInfo);
default:
throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(
loginResult.Result,
externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Email) ?? externalLoginInfo.ProviderKey,
loginResult.Tenant?.Name
);
}
}
Then your users will be able to login without specifying a tenant.
Conclusion
For a more stable UI, you can remove the tenant selection model used for login operations.
Go to *.Web.Mvc\Views\Account\Login.cshtml
and add following code part
@{
ViewBag.DisableTenantChange = true;
}
Login Page With Tenant Change
Login Page Without Tenant Change