Contact Us

Sign In Without Specifying Tenant (Angular)

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 with tenant change

Login Page Without Tenant Change login page without tenant change

ASP.NET Zero Awarded As One of the Best 5 Application Development Tools by Get App Logo Learn More