Sign In Without Specifying Tenant in ASP.NET Zero Angular
Normally, ASP.NET Zero uses tenant information during the login process. This document shows you how to implement the login process without tenant information.
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 TokenAuthController
Then, go to
*.Core\Controllers\TokenAuthController
.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
Authenticate([FromBody] AuthenticateModel model)
with the following content
[HttpPost]
public async Task<AuthenticateResultModel> Authenticate([FromBody] AuthenticateModel model)
{
if (UseCaptchaOnLogin())
{
await ValidateReCaptcha(model.CaptchaResponse);
}
var loginResult = await GetLoginResultAsync(
model.UserNameOrEmailAddress,
model.Password,
await GetTenancyNameOrNull(model.UserNameOrEmailAddress)//use new GetTenancyNameOrNull method that you add previously
);
var returnUrl = model.ReturnUrl;
if (model.SingleSignIn.HasValue && model.SingleSignIn.Value &&
loginResult.Result == AbpLoginResultType.Success)
{
loginResult.User.SetSignInToken();
returnUrl = AddSingleSignInParametersToReturnUrl(model.ReturnUrl, loginResult.User.SignInToken,
loginResult.User.Id, loginResult.User.TenantId);
}
//Password reset
if (loginResult.User.ShouldChangePasswordOnNextLogin)
{
loginResult.User.SetNewPasswordResetCode();
return new AuthenticateResultModel
{
ShouldResetPassword = true,
PasswordResetCode = loginResult.User.PasswordResetCode,
UserId = loginResult.User.Id,
ReturnUrl = returnUrl
};
}
//Two factor auth
await _userManager.InitializeOptionsAsync(loginResult.Tenant?.Id);
string twoFactorRememberClientToken = null;
if (await IsTwoFactorAuthRequiredAsync(loginResult, model))
{
if (model.TwoFactorVerificationCode.IsNullOrEmpty())
{
//Add a cache item which will be checked in SendTwoFactorAuthCode to prevent sending unwanted two factor code to users.
_cacheManager
.GetTwoFactorCodeCache()
.Set(
loginResult.User.ToUserIdentifier().ToString(),
new TwoFactorCodeCacheItem()
);
return new AuthenticateResultModel
{
RequiresTwoFactorVerification = true,
UserId = loginResult.User.Id,
TwoFactorAuthProviders = await _userManager.GetValidTwoFactorProvidersAsync(loginResult.User),
ReturnUrl = returnUrl
};
}
twoFactorRememberClientToken = await TwoFactorAuthenticateAsync(loginResult.User, model);
}
// One Concurrent Login
if (AllowOneConcurrentLoginPerUser())
{
await _userManager.UpdateSecurityStampAsync(loginResult.User);
await _securityStampHandler.SetSecurityStampCacheItem(loginResult.User.TenantId, loginResult.User.Id,
loginResult.User.SecurityStamp);
loginResult.Identity.ReplaceClaim(new Claim(AppConsts.SecurityStampKey,
loginResult.User.SecurityStamp));
}
var refreshToken = CreateRefreshToken(await CreateJwtClaims(loginResult.Identity, loginResult.User,
tokenType: TokenType.RefreshToken));
var accessToken = CreateAccessToken(await CreateJwtClaims(loginResult.Identity, loginResult.User,
refreshTokenKey: refreshToken.key));
return new AuthenticateResultModel
{
AccessToken = accessToken,
ExpireInSeconds = (int) _configuration.AccessTokenExpiration.TotalSeconds,
RefreshToken = refreshToken.token,
RefreshTokenExpireInSeconds = (int) _configuration.RefreshTokenExpiration.TotalSeconds,
EncryptedAccessToken = GetEncryptedAccessToken(accessToken),
TwoFactorRememberClientToken = twoFactorRememberClientToken,
UserId = loginResult.User.Id,
ReturnUrl = returnUrl
};
}
- Replace the function named
ExternalAuthenticate([FromBody] ExternalAuthenticateModel model)
with the following content
[HttpPost]
public async Task<ExternalAuthenticateResultModel> ExternalAuthenticate([FromBody] ExternalAuthenticateModel model)
{
var externalUser = await GetExternalUserInfo(model);
var loginResult = await _logInManager.LoginAsync(new UserLoginInfo(model.AuthProvider, model.ProviderKey, model.AuthProvider));
switch (loginResult.Result)
{
case AbpLoginResultType.Success:
{
var refreshToken = CreateRefreshToken(await CreateJwtClaims(loginResult.Identity, loginResult.User,
tokenType: TokenType.RefreshToken));
var accessToken = CreateAccessToken(await CreateJwtClaims(loginResult.Identity, loginResult.User,
refreshTokenKey: refreshToken.key));
var returnUrl = model.ReturnUrl;
if (model.SingleSignIn.HasValue && model.SingleSignIn.Value &&
loginResult.Result == AbpLoginResultType.Success)
{
loginResult.User.SetSignInToken();
returnUrl = AddSingleSignInParametersToReturnUrl(model.ReturnUrl, loginResult.User.SignInToken,
loginResult.User.Id, loginResult.User.TenantId);
}
return new ExternalAuthenticateResultModel
{
AccessToken = accessToken,
EncryptedAccessToken = GetEncryptedAccessToken(accessToken),
ExpireInSeconds = (int) _configuration.AccessTokenExpiration.TotalSeconds,
ReturnUrl = returnUrl,
RefreshToken = refreshToken.token,
RefreshTokenExpireInSeconds = (int) _configuration.RefreshTokenExpiration.TotalSeconds
};
}
case AbpLoginResultType.UnknownExternalLogin:
{
var newUser = await RegisterExternalUserAsync(externalUser);
if (!newUser.IsActive)
{
return new ExternalAuthenticateResultModel
{
WaitingForActivation = true
};
}
//Try to login again with newly registered user!
loginResult = await _logInManager.LoginAsync(
new UserLoginInfo(model.AuthProvider, model.ProviderKey, model.AuthProvider)
);
if (loginResult.Result != AbpLoginResultType.Success)
{
throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(
loginResult.Result,
model.ProviderKey,
loginResult?.Tenant?.Name
);
}
var refreshToken = CreateRefreshToken(await CreateJwtClaims(loginResult.Identity,
loginResult.User, tokenType: TokenType.RefreshToken)
);
var accessToken = CreateAccessToken(await CreateJwtClaims(loginResult.Identity,
loginResult.User, refreshTokenKey: refreshToken.key));
return new ExternalAuthenticateResultModel
{
AccessToken = accessToken,
EncryptedAccessToken = GetEncryptedAccessToken(accessToken),
ExpireInSeconds = (int) _configuration.AccessTokenExpiration.TotalSeconds,
RefreshToken = refreshToken.token,
RefreshTokenExpireInSeconds = (int) _configuration.RefreshTokenExpiration.TotalSeconds
};
}
default:
{
throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(
loginResult.Result,
model.ProviderKey,
loginResult?.Tenant?.Name
);
}
}
}
Then your project will be able to use without specifying tenant.
Conclusion
For a more stable UI, you can remove the tenant selection model used for login operations.
Go to src\account\account.component.ts
and add add login
to tenantChangeDisabledRoutes
tenantChangeDisabledRoutes: string[] = [
'select-edition',
'buy',
'upgrade',
'extend',
'register-tenant'
//...
'login'//add login
];
Login Page With Tenant Change
Login Page Without Tenant Change