Implementar conectores
Después de revisar la estructura de archivos del conector, discutamos la implementación y la idea principal de desarrollar un conector.
Revisaremos un ejemplo sencillo para conectores sociales y sin contraseña para que puedas construir tu conector con casi la misma idea.
En esta parte, no profundizaremos en los detalles de parámetros específicos (como config
) ya que no es el objetivo de esta guía. Los desarrolladores que implementen nuevos conectores deben leer los documentos proporcionados por los proveedores de servicios de terceros, y esos documentos deben detallar los parámetros.
Construir un conector social
Tomemos el conector de GitHub como ejemplo.
El flujo de autorización de la mayoría de los conectores sociales sigue el Flujo de Código de Autorización de OAuth.
La mayoría de los conectores sociales, obtienen un perfil de usuario con la autenticación de los usuarios finales siguiendo un esquema de dos pasos (asumiendo que todos los pasos tienen éxito):
- Iniciar una solicitud de autenticación y obtener la autenticación del usuario.
- Obtener
accessToken
utilizando unauthCode
otorgado por el proveedor del conector. - Solicitar un perfil de usuario accesible públicamente usando
accessToken
.
Para completar el flujo, necesitamos tener los siguientes tres métodos:
getAuthorizationUri
getAuthorizationUri
genera una URL de redirección que puede dirigir a los usuarios finales a la página que necesita la autenticación de los usuarios.
La interfaz está definida como GetAuthorizationUri
en @logto/connector-kit
.
Se te permite almacenar información esencial relacionada con el inicio de sesión usando setSession
(el segundo parámetro de entrada de GetAuthorizationUri
) para el método getUserInfo
.
Los parámetros listados son necesarios:
authorizationEndpoint
se puede encontrar en el sitio de documentación de OAuth de GitHub, que es la página donde el usuario final debe ir para la autenticaciónconfig
, que incluyeclientId
yclientSecret
en el escenario de GitHubstate
, una cadena aleatoria para probar CSRFredirectUri
de la página de destino después de la autenticación exitosa del usuario final
const getAuthorizationUri = async ({ state, redirectUri }) => {
const queryParameters = new URLSearchParams({
client_id: config.clientId, // `config` contiene las credenciales de tu aplicación de GitHub
redirect_uri: redirectUri,
state,
});
return `${authorizationEndpoint}?${queryParameters.toString()}`;
};
getAccessToken
getAccessToken
obtiene el token de acceso con el código de autorización emitido después de la autenticación exitosa de los usuarios finales.
Además de config
que mencionamos en el método anterior getAuthorizationUri
, también queremos obtener:
code
de autorización de los parámetros llevados a la página de destino de redirecciónaccessTokenEndpoint
, que es el punto final para obtener el token de acceso con el código de autorización
const getAccessToken = async (config: GithubConfig, code: string) => {
const { clientId: client_id, clientSecret: client_secret } = config;
const httpResponse = await got.post({
url: accessTokenEndpoint,
json: {
client_id,
client_secret,
code,
},
timeout: defaultTimeout,
});
const result = accessTokenResponseGuard.safeParse(qs.parse(httpResponse.body));
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
}
const { access_token: accessToken } = result.data;
assert(accessToken, new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));
return { accessToken };
};
getUserInfo
getUserInfo
obtiene información del usuario con el token de acceso obtenido en el paso anterior.
La interfaz está definida como GetUserInfo
en @logto/connector-kit
.
Para propósitos de inicio de sesión, puedes recuperar la información necesaria usando la función getSession
.
userInfoEndpoint
es el punto final que se utiliza para obtener información del usuario.
Puedes consultar documentos oficiales para encontrar información específica del usuario que se puede acceder en el punto final de información del usuario y el alcance correspondiente.
id
asignado por el proveedor de identidad del conector (en este caso, GitHub) es obligatorio; otra información es opcional. Si puedes obtener email
o phone
del perfil del usuario, ASEGÚRATE de que estén "verificados". También necesitas alinear las claves de retorno con los nombres de los campos en el perfil de usuario de Logto.
const getUserInfo = async (
data: { code: string; config: GithubConfig },
getSession: GetSession,
{ set: SetStorageValue, get: GetStorageValue }
) => {
const { code, config } = data;
const { accessToken } = await getAccessToken(config, code);
try {
const httpResponse = await got.get(userInfoEndpoint, {
headers: {
authorization: `token ${accessToken}`,
},
timeout: defaultTimeout,
});
const result = userInfoResponseGuard.safeParse(parseJson(httpResponse.body));
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
}
const { id, avatar_url: avatar, email, name } = result.data;
return {
id: String(id),
avatar: conditional(avatar),
email: conditional(email),
name: conditional(name),
};
} catch (error: unknown) {
if (error instanceof HTTPError) {
const { statusCode, body: rawBody } = error.response;
if (statusCode === 401) {
throw new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid);
}
throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(rawBody));
}
throw error;
}
};
Puedes encontrar la implementación completa aquí.
Para más detalles sobre los parámetros configurables, consulta el README del conector de GitHub o los documentos oficiales de GitHub.
El ejemplo que hemos estado discutiendo se basa en el tipo de concesión de Código de Autorización del protocolo OAuth, que se utiliza en el conector de GitHub de Logto. Sin embargo, vale la pena destacar que otro tipo de concesión, el tipo de concesión Implícita, también se puede usar para recuperar el perfil de un usuario, y de hecho, proporciona un access_token directamente en la respuesta de autenticación. A pesar de esta conveniencia, el tipo de concesión de Código de Autorización generalmente se recomienda sobre el tipo Implícito debido a su mayor seguridad.
También puedes construir un conector basado en OIDC u otros protocolos abiertos, depende de tu caso de uso así como de la compatibilidad del proveedor social al que deseas conectarte.
Construir un conector sin contraseña
Revisemos la implementación del conector de correo directo de Aliyun para comprender el proceso de construir un conector sin contraseña.
Los conectores sin contraseña se utilizan para enviar un código aleatorio al correo electrónico o teléfono de los usuarios finales. Como resultado, se requiere un método sendMessage
.
sendMessage
Para enviar un mensaje, necesitamos que config
y endpoint
estén configurados correctamente.
endpoint
es el punto final al que se conectan tus llamadas APIconfig
contienetemplates
(plantillas de contenido para enviar el código de acceso en diferentes flujos de usuario),clientId
yclientSecret
(para acceder a las solicitudes de API)
const sendMessage = async (data, inputConfig) => {
const { to, type, payload } = data;
const config = inputConfig ?? (await getConfig(defaultMetadata.id));
validateConfig<AliyunDmConfig>(config, aliyunDmConfigGuard);
const { accessKeyId, accessKeySecret, accountName, fromAlias, templates } = config;
const template = templates.find((template) => template.usageType === type);
assert(
template,
new ConnectorError(
ConnectorErrorCodes.TemplateNotFound,
`Cannot find template for type: ${type}`
)
);
const parameters = {
AccessKeyId: accessKeyId,
AccountName: accountName,
ReplyToAddress: 'false',
AddressType: '1',
ToAddress: to,
FromAlias: fromAlias,
Subject: template.subject,
HtmlBody:
typeof payload.code === 'string'
? template.content.replace(/{{code}}/g, payload.code)
: template.content,
};
try {
const httpResponse = await request(
endpoint,
{ Action: 'SingleSendMail', ...staticConfigs, ...parameters },
accessKeySecret
);
const result = sendEmailResponseGuard.safeParse(parseJson(httpResponse.body));
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
}
return result.data;
} catch (error: unknown) {
if (error instanceof HTTPError) {
const {
response: { body: rawBody },
} = error;
assert(typeof rawBody === 'string', new ConnectorError(ConnectorErrorCodes.InvalidResponse));
errorHandler(rawBody);
}
throw error;
}
};
const request = async (
url: string,
parameters: PublicParameters & Record<string, string>,
accessKeySecret: string
) => {
const finalParameters: Record<string, string> = {
...parameters,
SignatureNonce: String(Math.random()),
Timestamp: new Date().toISOString(),
};
const signature = getSignature(finalParameters, accessKeySecret, 'POST');
const payload = new URLSearchParams();
for (const [key, value] of Object.entries(finalParameters)) {
payload.append(key, value);
}
payload.append('Signature', signature);
return got.post({
url,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
form: payload,
});
};
Puedes encontrar la implementación completa aquí.
Para más detalles sobre los parámetros configurables, consulta el README del conector de correo directo de Aliyun o los documentos oficiales de correo directo de Aliyun.
¿Qué más?
Para ver la definición de los métodos del conector y construir una imagen del diseño de la interfaz del conector, consulta @logto/connector-kit
. También puedes encontrar la referencia de ConnectorMetadata en "Connectors - ConnectorMetadata" y "Estructura de archivos del conector" que pueden ayudarte a organizar tu implementación.
- El esquema de Zod de la configuración de un conector es obligatorio para todos los conectores. Esto es bastante importante ya que realizamos una verificación de tipo antes de guardar
config
en la base de datos y llamar a las API que requieren información deconfig
. - Todos los conectores de SMS y conectores de correo electrónico requieren un método
sendMessage
para llamar a las API de envío de mensajes de los proveedores de servicios utilizando configuraciones de la base de datos. Los desarrolladores también pueden reutilizar este método para enviar un mensaje de prueba con una configuración no guardada mientras configuran los conectores en la Consola de Administración. - El generador de URL de autorización
getAuthorizationUri
y el recuperador de perfil de usuariogetUserInfo
son necesarios para todos los conectores sociales (getAccessToken
se considera un paso opcional engetUserInfo
). - Todos los métodos de los conectores funcionan a través de llamadas API, como resultado, los desarrolladores de conectores necesitan revisar documentos y manejar posibles respuestas de llamadas API no exitosas.
Instalar tus propios conectores
Asumimos que ya has terminado de construir tu propio conector. Sigue los siguientes pasos para instalarlo manualmente:
- Copia la carpeta del conector que implementaste en el directorio
/packages/connectors
delogto-io/logto
. - Instala las dependencias del repositorio del conector escribiendo
pnpm pnpm:devPreinstall && pnpm i
en la ruta raíz de la carpeta logto. - Construye el conector con
pnpm connectors build
. - Enlaza los conectores locales usando
pnpm cli connector link
. - Reinicia la instancia de Logto con
pnpm dev
en el directorio raíz delogto-io/logto
, y podrás encontrar los conectores instalados con éxito.
Ahora puedes probar y verificar tu conector para ver si funciona como se espera.
Si deseas agregar conectores que ya han sido publicados en NPM o conectores oficiales de Logto, puedes consultar Usando Logto CLI - Gestionar conectores.