Añadir un nuevo método de autenticación 'gestionado' a Discourse

Continuando desde Future Social Authentication Improvements

Ahora estamos en proceso de mover toda la información de ‘cuentas asociadas’ a una única tabla de base de datos. Esto ayudará a reducir significativamente la lógica duplicada y permitirá un desarrollo más rápido en el futuro. Por ejemplo, migrar nuestra lógica central de Twitter al nuevo sistema redujo la cantidad de líneas de código de 136 a solo 24 :tada:.

Esta publicación no está diseñada para ser un manual de instrucciones paso a paso para agregar un nuevo proveedor de autenticación, pero tiene como objetivo proporcionar una visión general, señalando el código fuente relevante donde sea necesario.

Implementando un autenticador

Cada autenticador debe implementar una subclase de Auth::Authenticator. Para usar la nueva lógica compartida, el autenticador puede en su lugar extender Auth::ManagedAuthenticator. Un ejemplo de una implementación básica se puede encontrar en el autenticador central de Facebook:

name, enabled? y register_middleware deben ser sobrescritos por las clases que implementen esta interfaz.

:information_source: Nota: para la compatibilidad multisitio, es importante que cualquier información específica del sitio se proporcione a omniauth en una lambda setup, en lugar de ser fija en el momento de la definición. Consulte todos los autenticadores centrales para ver ejemplos de esto.

Toda la lógica para vincular cuentas externas con cuentas de Discourse es manejada por Auth::ManagedAuthenticator. Esto depende de que el proveedor omniauth devuelva datos en el formato definido en su documentación. Si se requiere alguna manipulación de estos datos, los Autenticadores pueden sobrescribir el método after_authenticate y manipular el auth_token según sea necesario. Por ejemplo, el autenticador central de Twitter elimina toda la información extra del token:

Los datos se almacenan en la tabla de base de datos user_associated_accounts. provider_uid, info, credentials y extra se toman directamente de los datos devueltos por omniauth.

Una vez que se ha definido una clase Authenticator, necesita ser registrada. Esto debe ocurrir temprano en el ciclo de vida de la aplicación y no puede ocurrir dentro del método after_initialize de un plugin. El registro mínimo puede contener simplemente una referencia al autenticador. En un plugin, el registro se puede realizar usando la función auth_provider. Por ejemplo:

auth_provider authenticator: OpenIDConnectAuthenticator.new()

En el núcleo, el registro tiene lugar en discourse.rb. Una lista completa de las opciones posibles de AuthProvider se puede encontrar aquí. El contenido de texto puede ser definido usando estas opciones, pero es mejor proporcionar cadenas localizables en client.en.yml siguiendo las claves estándar. Por ejemplo:

Notas adicionales sobre ManagedAuthenticator por @fantasticfears

ManagedAuthenticator en detalle

Es posible que necesites trabajar en algo especial para la autenticación. Y te gustaría saber más sobre ManagedAuthenticator. Básicamente, tiene varias operaciones, opciones y controla cómo se utilizarán los datos.

Discourse gestiona la información de usuario con dos controladores. Users::OmniauthCallbacksController gestiona la carga útil una vez que la autenticación OAuth2 está completada. after_authenticate se llama aquí. can_connect_existing_user? también se utiliza aquí.
Hay algunos métodos privados que puedes leer para entender cómo funcionan los diferentes campos de datos.

if authenticator.can_connect_existing_user? && current_user
  @auth_result = authenticator.after_authenticate(auth, existing_account: current_user)
else
  @auth_result = authenticator.after_authenticate(auth)
end

UsersController tiene revoke_account que usa can_revoke? y revoke. Pero para que el método revoke funcione de forma remota, necesitas construir tu propia implementación.

UserAuthenticator es una clase de servicio que ayuda a autenticar (verificando la confirmación de correo electrónico o la ruta OAuth2) usuarios. after_create_account se llama aquí.

La lógica central permanece en after_authenticate con la clase de datos Auth::Result. Seguimos la estructura de datos aquí. extra_data se pasará a after_create_account para crear registros relacionados.

result.extra_data = {
  provider: auth_token[:provider],
  uid: auth_token[:uid],
  info: auth_token[:info],
  extra: auth_token[:extra],
  credentials: auth_token[:credentials]
}

Intentará coincidir y conectarse con una cuenta existente.

Quizás te preguntes por qué la creación automática de cuentas es posible pero no hay User.create. Esto se hace en UsersController#create.

authentication = UserAuthenticator.new(user, session)

El usuario es una instancia nueva que se poblará con datos de sesión preparados por el proveedor de autenticación. Confía en mí, es solo magia.


Migración al nuevo sistema

Para proporcionar una transición sin problemas al nuevo sistema, los datos deben migrarse desde la ubicación de almacenamiento antigua. Para los proveedores de autenticación centrales, esto pueden ser tablas dedicadas. Para los plugins, esto puede ser plugin_store_rows, o oauth2_user_infos. Los datos mínimos requeridos en una fila de user_associated_accounts son provider_name, provider_uid y user_id. Para un ejemplo de migración ver:

Una vez que el sistema ManagedAuthenticator haya sido liberado en la rama estable con v2.2.0, comenzaremos a migrar los plugins de autenticación oficiales. En ese punto, se añadirá aquí un ejemplo de migración de plugin_store_row.


Este documento está bajo control de versiones - sugiera cambios en github.

23 Me gusta

@david todo el trabajo realizado aquí es súper bueno. Lo aprecio mucho. También tuve la oportunidad de jugar con GitHub - discourse/discourse-development-auth: A discourse plugin which adds a fake authentication provider. For development purposes only., que es muy útil.

Solo una advertencia, la función no funciona bien (no muestra la ventana emergente de registro) con ember cli en local. Me rompí la cabeza mientras escribía un plugin para agregar un proveedor de autenticación y de repente se me ocurrió usar NO_EMBER_CLI=1 y todo empezó a funcionar.

7 Me gusta

Me gustaría saber si implementar un autenticador sería el camino correcto hacia

¿Entiendo correctamente que todos los autenticadores registrados se llaman al principio de la aplicación, por lo que podría probar allí si el nombre de usuario y alguna pista para la autenticación por correo electrónico están incluidos en la URL y mostrar un formulario de “enviarme un enlace de inicio de sesión” como respuesta?