Cómo volver a iniciar sesión como usuario de forma silenciosa mediante la autenticación IdentityServer4

Aug 20 2020

Tenemos una solución basada en el marco ABP de terceros y su arquitectura multicapa:

Estamos utilizando Angular como cara web e IdentityServer4 para la autenticación de usuarios. Entonces, estamos ejecutando 2 hosts: el host de la API HTTP y el host de IdentityServer y, en cuanto a la cara web, funciona de manera estándar: cuadro de inicio de sesión, el usuario ingresa las credenciales, listo.

Sin embargo, tenemos una configuración personalizada que permite el mismo nombre de inicio de sesión con un inquilino diferente. La lista de inquilinos se muestra como un menú desplegable en la interfaz de usuario y nos gustaría volver a iniciar la sesión de un usuario con el inquilino seleccionado en lugar del usuario que inició sesión actualmente. Debe verse como una simple recarga de página. El problema es que no tengo una comprensión clara de cómo implementar esto. Intenté usar la siguiente llamada desde la capa de aplicación, pero no funciona (el error es "No hay ningún controlador de autenticación registrado para el esquema 'Identity.Application' ...", pero no sé cómo configurar configuración de autenticación en la capa de aplicación para poder trabajar con nuestro IdentityServer):

    [HttpGet]
    public async Task<TenantDto> SwitchTenantForCurrentUser(Guid? tenantId)
    {
        var abxUser = await _abxUserRepository.FirstOrDefaultAsync(x => x.Login == CurrentUser.UserName && x.Tenant.AbpId == tenantId);

        if (abxUser == null)
            return null;

        using var _ = _abpCurrentTenant.Change(tenantId);

        var currentTenant = await _abxTenantRepository.FirstOrDefaultAsync(x => x.AbpId == _abpCurrentTenant.Id.Value);
        var identityUser = await _identityUserRepository.FindByNormalizedUserNameAsync(abxUser.Login.ToUpper());

        if (await _signInManager.CanSignInAsync(identityUser))
        {
            await _signInManager.SignOutAsync();
            await _signInManager.SignInAsync(identityUser, true);
        }

        return ObjectMapper.Map<Tenant, TenantDto>(currentTenant); // Not decided yet what to return, it depends on proper implementation
    }

Parte de la configuración del host de la API Http en cuanto a la autenticación:

    private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
    {
        context.Services.AddAuthentication("Bearer")
            .AddIdentityServerAuthentication(options =>
            {
                options.Authority = configuration["AuthServer:Authority"];
                options.RequireHttpsMetadata = true;
                options.ApiName = "CentralTools";
                options.JwtBackChannelHandler = new HttpClientHandler
                {
                    //TODO: use valid certificate in future and change the logic
                    ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
                };
            });

        context.Services.AddAbpIdentity().AddDefaultTokenProviders();

Respuestas

1 Alexander Aug 26 2020 at 16:49

Misión cumplida. Pasos:

  1. BACK-END: implementar y registrar un validador de tipo de concesión personalizado en el proyecto IdentityServer:

    SwitchToTenantGrantValidator: IdentityServer4.Validation.IExtensionGrantValidator

En resumen, ValidateAsync acepta los datos del usuario autenticado (su token de acceso, ID de inquilino, etc.) y toma la decisión si el usuario debe ingresar. El método escribe los datos del inquilino objetivo en el objeto de resultado de contexto;

  1. FRONT-END: realiza una llamada a IdentityServer con el tipo de concesión personalizado dado, proporcionando los datos necesarios para (1). Usamos Angular, así que tuve que extender OAuthService para admitir una solicitud de tipo de concesión personalizada;

  2. Ponga todo en orden (si (2) tuvo éxito) para mostrar los datos correctos en la interfaz de usuario: limpiar estados antiguos, etc.