Este foro ya no está activo, así que no puedes publicar nuevas preguntas ni responder a las preguntas existentes.

Autenticación de dos pasos con Symfony2

14 de marzo de 2015

Buenas, estoy intentando realizar un logín de dos etapas.

Tengo una aplicación donde los usuarios pertenecen a compañías, y cuando un usuario se loguea, debo verificar si este se encuentra en más de una compañía, ya que de ser así, debo mostrar una página para que el usuario seleccione con cuál de las compañías a las que pertenece quiere acceder a la plataforma.

Esto debe ser así ya que estamos migrando una aplicación a Symfony y la lógica de dicha aplicación en este aspecto debe mantenerse.

Los usuarios tienen perfiles (roles) diferentes por cada compañía a la que pertenecen y también manejan premios por compañía. Por lo tanto, cuando accede al sistema mucha de la lógica interna depende de la compañía con la que el usuario se encuentra logueado.

Entonces recapitulando mis necesidades son las siguientes:

Login
                |
         varias compañias? ---- Si ---- Pantalla (Seleccion Compañia)
                |                            |
                No                           |
                |                            |
       Los roles salen de una ---------------|
    Relación UserCompanyProfiles
                |
        Termina de loguear
  (Listeners de login Success y demás)

He intentando creando un SimpleFormAuthenticator, pero no logro implementar de forma limpia la redirección hacia la página de login o la de selección de compañía dependiendo del caso.

También intenté implementar un proveedor de autenticación con su respectivo EntryPoint, lo cual me permitió hasta cierto punto redirigir a login o a select compañía en base a diferentes tokens que implementé (UsernamePasswordToken y MultipleCompaniesToken).

Por otro lado intenté logueando al usuario, y luego en una acción de un controlador se hace la selección de la compañía (Un listener kernel.request manda a esta acción si el token es MultipleCompaniesToken), donde al hacer la selección se actualiza el token en TokenStorage, pero entonces no se ejecutan los eventos pertinentes al logueo del componente security.

Como ven se me ha hecho complejo este asunto, por lo que quería saber si han tenido este inconveniente y cómo lo han solucionado.

Desde ya muchas gracias!


Respuestas

#1

Antes que nada, admito que nunca me ha tocado hacer un login en dos pasos con Symfony. Tu solución parece correcta, pero como tú mismo dices, en mi opinión es demasiado compleja. La pregunta que me gustaría hacerte es: ¿no sería suficiente con jugar con el evento security.interactive_login después de la primera autenticación?

  • El usuario introduce su usuario + contraseña y se autentica en el sistema.
  • En el evento security.interactive_login haces una consulta para ver si el usuario está relacionado con más de una empresa.
  • Si el usuario sólo tiene una empresa, seguir adelante sin ejecutar el segundo paso del login.
  • Si el usuario tiene varias empresas, mostrarlas en una lista en la pantalla del segundo paso del login.

Luego en la entidad que representa al usuario podrías tener una propiedad llamada empresaActiva para saber en todo momento con qué empresa se ha relacionado el usuario.

@javiereguiluz

15 marzo 2015, 21:52
#2

Hola Javier, muchas gracias por tu respuesta.

Luego de muchos intentos de implementar SecurityFactories, autenticadores y demás, no dí con una solución limpia que permitiese lograr el cometido, sin embargo pude aprender bastante sobre el componente de seguridad :) jejeje.

Lo que he hecho es precisamente lo que has dicho, el login normal y corriente, y además implementé un SuccessHandler (que es el que permite establecer un response) para verificar las empresas, y si tiene varias, lo mando a la pagina de selección de empresa.

Por otro lado estoy implementando un listener para que verifique que el usuario en sesión tenga una empresa activa, y si no la tiene (y no está en la página de selección de empresas) lanzo una excepcion propia NotActiveCompanyFoundException.

Al firewall de seguridad le he implementado un EntryPoint que cuando hay una excepción de seguridad verifica si es del tipo NotActiveCompanyFoundException y manda a la página de selección de empresas.

Así puedo controlar que los usuarios sin empresa activa accedan en la aplicación.

Por último tengo un listener que funciona parecido al SwitchUser, esperando un parametro que contiene el id de una empresa, para poder establecer/actualizar la empresa activa del usuario.

Hasta este punto veo que voy a necesitar otros listeners, ya que los usuarios deben aceptar unos terminos y condiciones la primera ves que acceden con cierta empresa a la aplicación, por lo que al establecerse la compañia activa, debo aplicar la misma lógica expuesta anteriormente.

Saludos!

@manuel_j555

16 marzo 2015, 20:39
#3

Leyendo tu respuesta me queda clara una cosa: podrías dar una charla espectacular contando con detalle este caso de uso y profundizando en las posibilidades del componente de seguridad de Symfony. Muchas gracias por compartirlo con nosotros.

@javiereguiluz

17 marzo 2015, 15:08
#4

En tener un tiempo voy a intentar crear un post sobre la solución implementada, por los momentos estoy a horas de salir de vacaciones de la empresa, así que estaré alejado del desarrollo unos días :)

@manuel_j555

18 marzo 2015, 12:46
#5

Hola,

Les comparto mi experiencia tratando de hacer un login en 2 pasos con symfony 3.

1 Cree un bundle que se extiende de FOSUser.

1.1 Luego sobreescribi la clase UserChecker en el nuevo bundle.

En el metodo checkPreAuth agregue estas lineas lara inicializa unas variables de session.

$session = $this->request->getMasterRequest()->getSession();
        $session->set('user_id', null);

En el metodo checkPostAuth seteo unas variables que necesito para asignar una empresa.

$masterRequest = $this->request->getMasterRequest();
        $request = $masterRequest->request;
        $session = $masterRequest->getSession();
        $session->set('_security.last_user_id', $user->getId());
        $session->set('_security.last_password', $request->get('_password'));
        $companyForm=$request->get('form');
 
        if(!empty($companyForm['company_id']))
        {
            if($user->hasCompanyId($companyForm['company_id'])){
                $session->set('company_id', $companyForm['company_id']);
            }else{
                $ex = new CompanyNotFoundException('El susuario no tiene asignado la Empresa.');
                $ex->setUser($user);
                throw $ex;
            }
        }else{
            $ex = new CompanyNotFoundException('Por favor ingrese la Empresa.');
            $ex->setUser($user);
            throw $ex;
        }

1.2 La declaracion del servicio quedo asi.

app.user_checker:

class: AppBundle\?\?\UserChecker
 
    arguments: [ "@request_stack" ]
 
    scope: request
 
    public: true

1.3 En el nuevo bundle sobreescribi el loginAction

public function loginAction(Request $request)
  {
        /** @var $session \Symfony\Component\HttpFoundation\Session\Session */
        $session = $request->getSession();
        $view=null;
        $user_id=$session->get('_security.last_user_id');
        $repo = $this->getDoctrine()->getRepository('AppSecurityBundle:User');
        $user = $repo->findOneById($user_id);
        $lastPassword=null;
        if(!is_null($user))
        {
            $form = $this->createFormBuilder(array())
            ->add('company_id', 'entity', array(
                'class' => 'AppBunble\?\Entity\Company',
                'label' => 'Empresas',
                'choices' => $user->getCompanys(),
                'multiple' => false,
                'expanded' => false,
                'required' => false,
                    ))
            ->getForm();
            $data = $form->getData();
            $view=$form->createView();
            $lastPassword=$session->get('_security.last_password');
        }

1.4 En el template de login.html.twing agregue lo siguiente.

{% if form_company %}
        {#{ form_label(form_company.company_id) }#}
        {{ form_widget(form_company.company_id, {'attr': {'class': 'form-control', 'placeholder':'Empresa'}})}}
    {% endif %}

Espero sirva de ayuda .

Saludos

@cesarluisl

13 enero 2017, 16:04