Connectors implementieren
Nachdem wir uns die Dateistruktur des Connectors angesehen haben, lassen Sie uns die Implementierung und die Hauptidee der Entwicklung eines Connectors besprechen.
Wir werden ein einfaches Beispiel für soziale und passwortlose Connectors durchgehen, damit Sie Ihren Connector mit fast derselben Idee erstellen können.
In diesem Teil gehen wir nicht tief in die Details spezifischer Parameter (wie config
) ein, da dies nicht der Punkt dieses Leitfadens ist. Entwickler, die neue Connectors implementieren, sollten die von Drittanbietern bereitgestellten Dokumente lesen, und diese Dokumente sollten die Parameter im Detail erläutern.
Einen sozialen Connector erstellen
Nehmen wir den GitHub-Connector als Beispiel.
Der Autorisierungsfluss der meisten sozialen Connectors folgt dem OAuth Authorization Code Flow.
Die Mehrheit der sozialen Connectors folgt einem zweistufigen Schema, um ein Benutzerprofil mit der Authentifizierung der Endbenutzer zu erhalten (angenommen, dass alle Schritte erfolgreich sind):
- Eine Authentifizierungsanfrage starten und die Authentifizierung des Benutzers erhalten.
Zugangstoken (Access token)
mit einem vom Connector-Anbieter gewährtenauthCode
abrufen.- Ein öffentlich zugängliches Benutzerprofil mit
Zugangstoken (Access token)
anfordern.
Um den Fluss zu vervollständigen, benötigen wir die folgenden drei Methoden:
getAuthorizationUri
getAuthorizationUri
generiert eine Umleitungs-URL, die Endbenutzer zur Seite leiten kann, die die Authentifizierung der Benutzer erfordert.
Die Schnittstelle ist als GetAuthorizationUri
in @logto/connector-kit
definiert.
Sie dürfen wesentliche Informationen zur Anmeldung mit setSession
(dem zweiten Eingabeparameter von GetAuthorizationUri
) speichern, um der getUserInfo
-Methode zu dienen.
Folgende Parameter sind erforderlich:
authorizationEndpoint
kann in der GitHub OAuth-Dokumentationsseite gefunden werden, dies ist die Seite, zu der der Endbenutzer zur Authentifizierung gehen sollteconfig
, dasclientId
undclientSecret
im GitHub-Szenario enthältstate
, ein zufälliger String zur CSRF-PrüfungredirectUri
der Zielseite nach erfolgreicher Authentifizierung des Endbenutzers
const getAuthorizationUri = async ({ state, redirectUri }) => {
const queryParameters = new URLSearchParams({
client_id: config.clientId, // `config` enthält Ihre GitHub-Anwendungsanmeldeinformationen
redirect_uri: redirectUri,
state,
});
return `${authorizationEndpoint}?${queryParameters.toString()}`;
};
getAccessToken
getAccessToken
erhält das Zugangstoken mit dem Autorisierungscode, der nach erfolgreicher Authentifizierung der Endbenutzer ausgestellt wurde.
Neben config
, das wir in der vorherigen getAuthorizationUri
-Methode erwähnt haben, möchten wir auch Folgendes erhalten:
- Autorisierungs-
code
aus den Parametern, die zur Umleitungszielseite gebracht werden accessTokenEndpoint
, das der Endpunkt ist, um das Zugangstoken mit dem Autorisierungscode zu erhalten
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
ruft Benutzerinformationen mit dem in den vorherigen Schritten erhaltenen Zugangstoken ab.
Die Schnittstelle ist als GetUserInfo
in @logto/connector-kit
definiert.
Für Anmeldezwecke können Sie notwendige Informationen mit der Funktion getSession
abrufen.
userInfoEndpoint
ist der Endpunkt, der verwendet wird, um Benutzerinformationen zu erhalten.
Sie können offizielle Dokumente überprüfen, um spezifische Benutzerinformationen zu finden, die am Benutzerinfo-Endpunkt zugänglich sind, und die entsprechende Berechtigung.
id
, das vom Identitätsanbieter des Connectors (in diesem Fall GitHub) zugewiesen wird, ist erforderlich; andere Informationen sind optional. Wenn Sie email
oder phone
aus dem Benutzerprofil erhalten können, STELLEN SIE SICHER, dass sie "verifiziert" sind. Sie müssen auch die zurückgegebenen Schlüssel mit den Feldnamen im Logto-Benutzerprofil abgleichen.
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;
}
};
Die vollständige Implementierung finden Sie hier.
Für weitere Details zu konfigurierbaren Parametern siehe GitHub-Connector-README oder die offiziellen GitHub-Dokumente.
Das Beispiel, das wir besprochen haben, basiert auf dem Autorisierungscode-Grant-Typ des OAuth-Protokolls, der im GitHub-Connector von Logto verwendet wird. Es ist jedoch erwähnenswert, dass ein anderer Grant-Typ, der implizite Grant-Typ, ebenfalls verwendet werden kann, um das Profil eines Benutzers abzurufen, und tatsächlich ein Zugangstoken direkt in der Authentifizierungsantwort bereitstellt. Trotz dieser Bequemlichkeit wird der Autorisierungscode-Grant-Typ im Allgemeinen gegenüber dem impliziten Typ aufgrund seiner stärkeren Sicherheit empfohlen.
Sie können auch einen Connector basierend auf OIDC oder einigen anderen offenen Protokollen erstellen, es hängt von Ihrem Anwendungsfall sowie der Kompatibilität des sozialen Anbieters ab, mit dem Sie sich verbinden möchten.
Einen passwortlosen Connector erstellen
Lassen Sie uns die Implementierung des Aliyun Direct Mail Connectors durchgehen, um den Prozess des Aufbaus eines passwortlosen Connectors zu verstehen.
Passwortlose Connectors werden verwendet, um einen zufälligen Code an die E-Mail oder das Telefon der Endbenutzer zu senden. Daher ist eine sendMessage
-Methode erforderlich.
sendMessage
Um eine Nachricht zu senden, müssen config
und endpoint
korrekt eingerichtet sein.
endpoint
ist der Endpunkt, mit dem Ihre API-Aufrufe verbunden werdenconfig
enthälttemplates
(Inhaltsschablonen zum Senden von Passcodes in verschiedenen Benutzerflüssen),clientId
undclientSecret
(zum Zugriff auf API-Anfragen)
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,
});
};
Die vollständige Implementierung finden Sie hier.
Für weitere Details zu konfigurierbaren Parametern siehe Aliyun Direct Mail Connector README oder die offiziellen Aliyun Direct Mail-Dokumente.
Was noch?
Um die Definition der Connector-Methoden zu sehen und ein Bild des Connector-Schnittstellendesigns zu erstellen, siehe @logto/connector-kit
. Sie können auch die ConnectorMetadata-Referenz unter "Connectors - ConnectorMetadata" und "Connector-Dateistruktur" finden, die Ihnen helfen kann, Ihre Implementierung zu organisieren.
- Ein Connector-Config-Zod-Schema ist für alle Connectors obligatorisch. Dies ist sehr wichtig, da wir einen Typencheck durchführen, bevor wir
config
in der Datenbank speichern und APIs aufrufen, dieconfig
-Informationen erfordern. - Alle SMS-Connectors und E-Mail-Connectors erfordern eine
sendMessage
-Methode, um die Nachrichtensende-APIs der Dienstanbieter mit Konfigurationen aus der Datenbank aufzurufen. Entwickler können diese Methode auch wiederverwenden, um eine Testnachricht mit nicht gespeicherter Konfiguration zu senden, während sie Connectors in der Admin-Konsole einrichten. - Der Autorisierungs-URL-Generator
getAuthorizationUri
und der Benutzerprofil-AbrufgetUserInfo
sind für alle sozialen Connectors erforderlich (getAccessToken
wird als optionaler Schritt ingetUserInfo
angesehen). - Alle Methoden der Connectors arbeiten über API-Aufrufe, daher müssen Connector-Entwickler Dokumente überprüfen und mögliche erfolglose API-Antworten behandeln.
Installiere deine eigenen Connectors
Wir gehen davon aus, dass Sie bereits Ihren eigenen Connector erstellt haben. Gehen Sie die folgenden Schritte durch, um ihn manuell zu installieren:
- Kopieren Sie den von Ihnen implementierten Connector-Ordner in das Verzeichnis
/packages/connectors
vonlogto-io/logto
. - Installieren Sie die Abhängigkeiten des Connector-Repositorys, indem Sie
pnpm pnpm:devPreinstall && pnpm i
im Stammverzeichnis des Logto-Ordners eingeben. - Bauen Sie den Connector mit
pnpm connectors build
. - Verknüpfen Sie lokale Connectors mit
pnpm cli connector link
. - Starten Sie die Logto-Instanz mit
pnpm dev
im Stammverzeichnis vonlogto-io/logto
neu, und Sie können sehen, dass die Connectors erfolgreich installiert wurden.
Sie können nun Ihren Connector testen und ausprobieren, um zu sehen, ob er wie erwartet funktioniert.
Wenn Sie Connectors hinzufügen möchten, die bereits auf NPM oder Logto offiziellen Connectors veröffentlicht wurden, können Sie Using Logto CLI - Manage connectors überprüfen.