Base solution for your next web application
Open Closed

Use Host Database for Authentication #5018


User avatar
0
OriAssurant created

I use MVC5.* + AngularJS and enabled multitenancy for the project. I separate out host database and tenant databases, keeping all tenant users in the host database and only using tenant database for application services.

So I hope that the workflow of login process would be: find user --> get user.tenantId -> find the connectionstring of the tenant -> route all app.services to the tenant database.

But seems there is an auto-filter on the tenant when I'm logging in. Suppose I'm trying to find a user who belongs to tenant 2 with either UserManager or HostDbContext, the query will automatically filter user by tenant = null as I didn't set UnitOfWorkManager.Current.SetTenantId before I search for the user. If I hard code UnitOfWorkManager.Current.SetTenantId to be 2 before I search, then I'm able to find the user.

var ur = await _userManager.Users.Where(u => u.PhoneNumber == model.MobilePhone).FirstOrDefaultAsync();

or

var hostDbContext = new HostDbContext();
                SqlParameter param = new SqlParameter()
                {
                    ParameterName = "@UserID",
                    SqlDbType = SqlDbType.BigInt,
                    Direction = ParameterDirection.Output
                };
                var t = hostDbContext.Database.ExecuteSqlCommand("select top 1 tenantId from abpusers where id = @UserID", param);

Could anyone give some hint on the way to find the user before I set the tenantId in UnitOfWorkManager?

Thank you,


8 Answer(s)
  • User Avatar
    0
    aaron created
    Support Team

    You can DisableFilter for IMayHaveTenant:

    using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.MayHaveTenant))
    {
        var ur = await _userManager.Users.Where(u => u.PhoneNumber == model.MobilePhone).FirstOrDefaultAsync();
    }
    

    Read the documentation on Data Filters.

  • User Avatar
    0
    OriAssurant created

    Thank you very much Aaron.

    After I disable the data filter, I'm able to get the user.

    But I'm not sure why when I run the _signInManager.SignInOrTwoFactor with disablefilter

    var loginResult=  new AbpLoginResult<Tenant, User>(tenant, user, createIdentity);
    signInResult = await _signInManager.SignInOrTwoFactor(loginResult, loginModel.RememberMe);
    

    , it still throws me error at hostDb constructor:

    public HostDbContext(string nameOrConnectionString)
                : base(nameOrConnectionString)
    

    Error Message: System.Data.SqlClient.SqlException: 'There is already an object named 'AbpAuditLogs' in the database.'

    The pass-in nameOrConnectionString is the connectionstring of tenant database (saved in abpTenants table on Host Database), which is supposed to be the connectionstring of host database. Tenant database doesn't contain any abp table. Seems the application is trying to create entities again because it doesn't find tables in the tenant database.

    How could I disable datafilter and user hostDb for _signInManager?

    Thank you,

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Because SignInOrTwoFactor switches to tenant context, see <a class="postlink" href="https://github.com/aspnetboilerplate/aspnetboilerplate/blob/dev/src/Abp.Zero.Owin/Authorization/AbpSignInManager.cs#L58">https://github.com/aspnetboilerplate/as ... ger.cs#L58</a>.

    So, you need to override several methods in SignInManager. Or you can move Tenant users to Tenant databases.

  • User Avatar
    0
    OriAssurant created

    Thank you @ismcagdas. Seems couple methods in SettingManager need to be overwritten as well..

  • User Avatar
    0
    OriAssurant created

    Hi ismcagdas,

    I overwrote the SignInManager class and successfully passed _signInManager.SignInOrTwoFactor(loginResult, loginModel.RememberMe);. Thank you very much for the clue.

    But after I returned from Login method (return Json(new AjaxResponse { TargetUrl = returnUrl })), the application went through Tenant.cs and passed the connection string of tenant database into the constructor of host database again:

    public HostDbContext(string nameOrConnectionString)
        : base(nameOrConnectionString)         _<-- error happened when tenantDb's connectionstring got passed in._
    {
    }
    

    How could I prevent using tenantDb's connectionstring in HostDbContext? Or how could I make sure I'm passing tenant connectionstring to tenant constructor?

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @OriAssurant,

    Sorry for our late response. It seems like the best option is to create a new implementation of IConnectionStringResolver and decide which connection string to use.

    How can you decide when to use host database or tenant database ? You can use "Ambient context pattern" for this. You need to set a parameter on the ambient context whenever you want to connect to host database.

    But in our opinion, this approach might also cause problems in the future. We think that it is better to keep users in tenant database when you are using a seperate database for a tenant.

  • User Avatar
    0
    OriAssurant created

    Thank you so much ismcagdas. That resolves my confusions and gives me better idea.

    I think it's better to save tenant users in tenant databases. I'm thinking of a workaround: first use AbpUserAccounts table (via IRepository<UserAccount, long> userAccountRepository) to determine which tenant the user belongs to and then go to to tenant database to perform login, which includes authentication, logging, etc.

    Do you see any downside of doing this?

    Thank you,

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @OriAssurant,

    For now, I can pnly suggest you to seperate transactions of querying the AbpUserAccounts table and login operation. I mean, first disable transaction for the login action, after that, create a two seperate transaction block in the login.

    In first block, query AbpUserAccounts table. In the second one, place the login code. If you don't want to do this, you might need to enable MSDTC on the server the app is running.