Starts in:
2 DAYS
9 HRS
34 MIN
39 SEC
Starts in:
2 D
9 H
34 M
39 S
Open Closed

Switching between different tenants of the same user #333


User avatar
0
alexander.nikonov created
  • ABP Framework version: v2.9
  • UI type: Angular
  • Tiered (MVC) or Identity Server Separated (Angular): Identity Server Separated

Hi, our business model requires one user to belong to several tenants. So we have created accounts for the same user, where each account has the same user name, but different tenant id.

  1. we need user to login once and next time his tenant changed, the user does not need to re-enter his credentials again - he re-logins automatically (SSO), for instance, after selecting the tenant from dropdown list in UI; Could you please suggest the solution?
  2. is there a way to get the list of all tenants of all user accounts using ABP framework?
  3. how to get the list of tenants where user name is the same?

11 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi

    we need user to login once and next time his tenant changed, the user does not need to re-enter his credentials again - he re-logins automatically (SSO), for instance, after selecting the tenant from dropdown list in UI; Could you please suggest the solution?

    If you are using the password grant type of the identity server to get the token, you can try rewrite AbpResourceOwnerPasswordValidator to implement your login again.

    is there a way to get the list of all tenants of all user accounts using ABP framework? how to get the list of tenants where user name is the same?

    You can use ICurrentTenant to switch tenants. Then use the related repository to query tenant and user information.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    We also plan implement the linked account . https://github.com/abpframework/abp/issues/2611

  • User Avatar
    0
    alexander.nikonov created

    Hi,

    switching current tenant works - thank you. But as to the first part of the question... we would prefer to authenticate and log in identity user after finding him in the repository, I hoped something like this would work (not dealing with password):

            using var _ = _abpCurrentTenant.Change(tenantId);
    
            var currentTenant = await _abxTenantRepository.FirstOrDefaultAsync(x => x.AbpId == _abpCurrentTenant.Id.Value);
            var identityUser = await _identityUserRepository.FindByNormalizedUserNameAsync(abxUser.Login.ToUpper());
            await _signInManager.SignInAsync(identityUser, true);
    
            return ObjectMapper.Map<Tenant, TenantDto>(currentTenant);
    

    However, this does not work.

    Am I on right track, all in all? What do I need to make this work (to make our IdentityServer take care on authenticating the selected identityUser and logging him in automatically, it needs to look like a simple page reload)?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    we would prefer to authenticate and log in identity user after finding him in the repository,

    Identity server does not seem to be able to do this, it uses tokens instead of cookies, so it cannot automatically log in.

    await _signInManager.SignInAsync(identityUser, true); is applicable for the cookie authentication scheme, because the browser can automatically update the cookie.

    Maybe the identity server can generate a new token on the server side, etc, but you need to make Angular get and use the new token.

  • User Avatar
    0
    alexander.nikonov created

    Hi, where I can find information (or better - test examples) how to work with Identity Server in ABP? Generate tokens, use Identity resources, grant types, etc. I saw doc on abp.io, but Identity Server section is very scarse. In fact, I'd like to know what each parameter in Identity Server section in Angular app means and how it is supposed to be used.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi @alexander.nikonov

    This is related to identityserver. abp just calls its api. So you should check the documentation of the Identiy server and its Github more.

    https://identityserver4.readthedocs.io/en/latest/ https://github.com/IdentityServer/IdentityServer4

  • User Avatar
    0
    alexander.nikonov created

    Could you please make a test project demonstrating how to accomplish our goal? I've spent a lot of time searching for solution on stackoverflow, analyzing ABP code, etc. and I am far behind the schedule. But the solution is still not working.

    The pieces of code I have ended up with are the following:

            [Authorize]
            public async Task<PagedResultDto<LookupDto<string>>> GetTenantsForCurrentUser()
            {
                if (!CurrentUser.IsAuthenticated)
                    return null;
    
                var items = await _abxUserRepository.FindTenantsByLoginAsync(CurrentUser.UserName);
    
                return new PagedResultDto<LookupDto<string>>
                {
                    TotalCount = items.Count(),
                    Items = ObjectMapper.Map<List<Tenant>, List<LookupDto<string>>>(items)
                };
            }
            
           <div *ngIf="user.isAuthenticated" class="dropdown btn-group" ngbDropdown>
              <a
                class="btn"
                role="button"
                id="dropdownMenuTenantsLink"
                ngbDropdownToggle
                *ngIf="(selectedTenant$ | async)?.name as tenantName"
              >
                <span>{{ '::Tenants:CurrentTenant' | abpLocalization }}: <b>{{ tenantName }}</b></span>
              </a>
              <div
                ngbDropdownMenu
                class="dropdown-menu dropdown-menu-right"
                *ngIf="dropdownTenants$ | async"
              >
                <h6 class="dropdown-header">{{ '::Tenants:SwitchTenant' | abpLocalization }}</h6>
                <a
                  *ngFor="let tenant of dropdownTenants$ | async"
                  class="dropdown-item pointer"
                  (click)="switchTenant(user.userName, tenant)"
                >
                  {{ tenant.displayName }}</a
                >
              </div>
            </div>
            
      switchTenant(userName: string, tenant: Common.Lookup<string>) {
        this.oAuthService.configure(
          this.store.selectSnapshot(ConfigState.getOne('environment')).oAuthConfig,
        );
    
        return from(this.oAuthService.loadDiscoveryDocument())
          .pipe(
              switchMap(() =>
                  from(
                      this.oAuthService.fetchTokenUsingGrant(
                          'Custom', // why we cannot use own name??
                          null,
                          new HttpHeaders(
                            { ...(tenant.id && { __tenant: tenant.id })}
                            && (userName && { user: userName })
                          )
                      ),
                  ),
              ),
              switchMap(() => this.store.dispatch(new GetAppConfiguration())),
              tap(() => {
                  const redirectUrl =
                  snq(() => window.history.state.redirectUrl) || (this.options || {}).redirectUrl || '/';
                  this.store.dispatch(new Navigate([redirectUrl]));
              }),
              take(1),
          ).subscribe(() => {});
      }
    

    All in all, the idea is:

    1. in app service layer to get the list of all tenants for current user (login name) - see above;

    2. fill dropdown of tenants for passwordless login and supply the tenant id for each entry - see above;

    3. make request from Angular app (using oAuthService->[some passwordless workflow?]) supplying the access token of currently authenticated user, some custom passwordless grant-type and tenant id in order to login (custom extension validator is going to intercept this request on server-side and approve or reject the login).

    4. is the most tricky part, since OAuthService still does not support custom grant type. I've tried to patch the latest angular-oauth2-oidc from github (10.x) and add fetchTokenUsingGrant, but how to update our solution to the patched version? Your framework uses this package inside and I even cannot see angular-oauth2-oidc via npm outdated.

  • User Avatar
    0
    hikalkan created
    Support Team Co-Founder

    Hi guys,

    I didn't read the whole conversation. But, to add a note: We will implement "account linking" feature in the next versions which allows you to have multiple account in the same or different tenant and relate these accounts, so you can switch between account with a single click.

  • User Avatar
    0
    alexander.nikonov created

    @maliming

    we have managed to do that by our owns:

    BACK-END: implemented & registered custom grant type validator in IdentityServer project:

    SwitchToTenantGrantValidator : IdentityServer4.Validation.IExtensionGrantValidator

    In short, ValidateAsync accepts the data of authenticated user (his access token, tenant ID, etc.) and makes the decision if the user has to be let in. The method writes data of target tenant into context result object;

    FRONT-END: made a call to IdentityServer with the given custom grant type, supplying data required for (1). We used Angular, so I had to extend OAuthService package to support custom grant type request;

    Still need to bring everything in order (if (2) was successful) to display correct data in UI: clean old states, etc. - since now old user is still displayed for some reason (despite dispatching new GetAppConfiguration);

    The question that is still open: why you hardcoded custom grant type names? When we added manually some custom name to DB - it worked. But in ABP Identity Server management page we cannot do that.

  • User Avatar
    1
    maliming created
    Support Team Fullstack Developer

    The question that is still open: why you hardcoded custom grant type names? When we added manually some custom name to DB - it worked. But in ABP Identity Server management page we cannot do that.

    I will try solve this problem in the next version. : )

    https://github.com/abpframework/abp/issues/5189

  • User Avatar
    0
    alexander.nikonov created

    Ok, thank you!

Made with ❤️ on ABP v9.1.0-preview. Updated on November 20, 2024, 13:06