Sign In Without Specifying Tenant
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
LogInManager. (It is located in aspnet-core\src\[YOURAPPNAME].Application\Authorization folder.)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
LogInManagerwill be able to use given user's tenant for login process.Updating UserManager
Go to
UserManager. (It is located in aspnet-core\src\[YOURAPPNAME].Core\Authorization\Users folder.)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
AccountController. (It is located in aspnet-core\src\[YOURAPPNAME].Mvc\Controllers folder.)Replace the function named
GetTenancyNameOrNullwith the following contentprivate 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.
More
For a more stable UI, you can remove the tenant selection model used for login operations.
Go to aspnet-core\src}[YOURAPPNAME].Web.Mvc\Views\Account\Login.cshtml and add following code part
@{
ViewBag.DisableTenantChange = true;
}