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

Continuando desde Future Social Authentication Improvements

Actualmente, estamos en proceso de mover toda la información de las «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 el número 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, sino que busca 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 de Facebook del núcleo:

name y register_middleware deben ser sobrescritos por las clases que lo implementen, junto con enable_setting, que es la configuración de sitio booleana que un administrador utiliza para activar el proveedor.

Un autenticador debería también declarar required_settings: las configuraciones de sitio que deben tener un valor antes de que la autenticación pueda tener éxito. La clase base las utiliza para configured?, y enabled? es enable_setting && configured?, por lo que un proveedor con credenciales faltantes nunca se anuncia en la página de inicio de sesión y su ruta /auth/<name> permanece cerrada; de lo contrario, hacer clic en el botón dejaría al usuario varado en la página de error del propio proveedor sin forma de regresar. Declarar required_settings también permite que AuthProviderCredentialsValidator se niegue a habilitar el proveedor en primer lugar; conéctalo con validator: "AuthProviderCredentialsValidator" en la configuración de habilitación.

def enable_setting
  :enable_google_oauth2_logins
end

def required_settings
  %i[google_oauth2_client_id google_oauth2_client_secret]
end

Un autenticador que sobrescribe enabled? directamente se excluye de ambas verificaciones.

: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 del núcleo 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 de 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 de Twitter del núcleo 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 definirse usando estas opciones, pero es mejor proporcionar cadenas localizables en client.en.yml siguiendo las claves estándar. Por ejemplo:

Notas adicionales de 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 usará los datos.

Discourse gestiona la información de usuario con dos controladores. Users::OmniauthCallbacksController gestiona el payload una vez que la autenticación OAuth2 está completa. 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) a los 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.

Es posible que 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 que son 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 del núcleo, 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 se haya 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á controlado por versiones - sugiere 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?