Zum Hauptinhalt springen

Authentifizierung zu deiner traditionellen Webanwendung hinzufügen

hinweis

Diese Anleitung geht davon aus, dass du eine Anwendung des Typs "Traditional web" in der Admin-Konsole erstellt hast.

Deine App läuft möglicherweise serverseitig anstatt im Browser, unter Verwendung von Frameworks wie Django, Laravel usw. Das wird als traditionelle Web-App bezeichnet. Wenn du auf dieser Seite kein geeignetes SDK findest, musst du wahrscheinlich manuell integrieren.

Dieser Artikel führt dich Schritt für Schritt durch den Prozess. Wir nehmen Express in Node.js als Beispiel.

tipp

Dieser Artikel ist nicht nur für Express gedacht, und wenn du andere Frameworks oder sogar andere Sprachen verwendest, kannst du @logto/js durch das Core-SDK der anderen Sprache ersetzen und einige der Schritte anpassen.

Quellcode erhalten

Du kannst auf GitHub gehen, um den endgültigen Code für diese Anleitung zu erhalten.

Ein Express-Projekt starten

Mit express-generator kannst du schnell ein Express-Projekt starten.

mkdir express-logto
cd express-logto
npx express-generator

Abhängigkeiten installieren

Die Demo-App benötigt 4 Abhängigkeiten:

  1. @logto/js: Logtos Core-SDK für JavaScript.
  2. node-fetch: Minimaler Code für eine window.fetch kompatible API auf Node.js-Laufzeit.
  3. express-session: Ein Session-Middleware, wir verwenden die Session, um Benutzertokens zu speichern.
  4. js-base64: Noch ein weiterer Base64-Transcoder.
npm i @logto/js node-fetch@v2 express-session js-base64

Session verwenden

Wenn Benutzer angemeldet sind, erhalten sie eine Reihe von Tokens (Zugangstoken, ID-Token, Auffrischungstoken) und Interaktionsdaten, und die Session ist ein ausgezeichneter Ort, um sie zu speichern.

Wir haben express-session im vorherigen Schritt installiert, also lass uns jetzt einfach den folgenden Code hinzufügen, um es einzurichten:

// app.js

const session = require('express-session');

app.use(
session({
secret: 'keyboard cat', // Ändere es zu deinem eigenen geheimen Schlüssel
cookie: { maxAge: 86400 },
})
);

Funktionen zur Authentifizierung von Benutzern implementieren

tipp

Wir gehen davon aus, dass die Anwendung auf http://localhost:3000 läuft in den folgenden Code-Snippets.

In diesem Schritt müssen wir die folgenden Authentifizierungsfunktionen implementieren:

  1. getSignInUrl: erstellt und gibt eine vollständige URL des Logto Authorization Servers zurück, zu dem Benutzer umgeleitet werden.
  2. handleSignIn: analysiert die Callback-URL, nachdem der Authentifizierungsprozess abgeschlossen ist, erhält den Code-Query-Parameter und holt dann Tokens (ein Zugangstoken, das Auffrischungstoken und ein ID-Token), um den Anmeldeprozess abzuschließen.
  3. refreshTokens: tauscht ein neues Zugangstoken mit dem Auffrischungstoken aus.
tipp

Du kannst das "App Secret" auf der Anwendungsdetailseite in der Admin-Konsole finden und kopieren:

App Secret
// logto.js

const {
withReservedScopes,
fetchOidcConfig,
discoveryPath,
generateSignInUri,
verifyAndParseCodeFromCallbackUri,
fetchTokenByAuthorizationCode,
fetchTokenByRefreshToken,
} = require('@logto/js');
const fetch = require('node-fetch');
const { randomFillSync, createHash } = require('crypto');
const { fromUint8Array } = require('js-base64');

const config = {
endpoint: 'https://logto.dev',
appId: 'foo',
appSecret: '<your-app-secret-copied-from-console>',
redirectUri: 'http://localhost:3000/callback', // Du musst es möglicherweise mit der Produktionsadresse deiner App ersetzen
scopes: withReservedScopes().split(' '),
};

const requester = (input, init) => {
const { appId, appSecret } = config;

return fetch(input, {
...init,
headers: {
...init?.headers,
authorization: `Basic ${Buffer.from(`${appId}:${appSecret}`, 'utf8').toString('base64')}`,
},
});
};

const generateRandomString = (length = 64) => {
return fromUint8Array(randomFillSync(new Uint8Array(length)), true);
};

const generateCodeChallenge = async (codeVerifier) => {
const encodedCodeVerifier = new TextEncoder().encode(codeVerifier);
const hash = createHash('sha256');
hash.update(encodedCodeVerifier);
const codeChallenge = hash.digest();
return fromUint8Array(codeChallenge, true);
};

const getOidcConfig = async () => {
return fetchOidcConfig(new URL(discoveryPath, config.endpoint).toString(), requester);
};

exports.getSignInUrl = async () => {
const { authorizationEndpoint } = await getOidcConfig();
const codeVerifier = generateRandomString();
const codeChallenge = await generateCodeChallenge(codeVerifier);
const state = generateRandomString();

const { redirectUri, scopes, appId: clientId } = config;

const signInUri = generateSignInUri({
authorizationEndpoint,
clientId,
redirectUri: redirectUri,
codeChallenge,
state,
scopes,
});

return { redirectUri, codeVerifier, state, signInUri };
};

exports.handleSignIn = async (signInSession, callbackUri) => {
const { redirectUri, state, codeVerifier } = signInSession;
const code = verifyAndParseCodeFromCallbackUri(callbackUri, redirectUri, state);

const { appId: clientId } = config;
const { tokenEndpoint } = await getOidcConfig();
const codeTokenResponse = await fetchTokenByAuthorizationCode(
{
clientId,
tokenEndpoint,
redirectUri,
codeVerifier,
code,
},
requester
);

return codeTokenResponse;
};

exports.refreshTokens = async (refreshToken) => {
const { appId: clientId, scopes } = config;
const { tokenEndpoint } = await getOidcConfig();
const tokenResponse = await fetchTokenByRefreshToken(
{
clientId,
tokenEndpoint,
refreshToken,
scopes,
},
requester
);

return tokenResponse;
};

Anmelden

Bevor wir in die Details eintauchen, hier ein kurzer Überblick über die Endbenutzererfahrung. Der Anmeldeprozess kann wie folgt vereinfacht werden:

  1. Deine App ruft die Anmeldemethode auf.
  2. Der Benutzer wird zur Logto-Anmeldeseite umgeleitet. Bei nativen Apps wird der Systembrowser geöffnet.
  3. Der Benutzer meldet sich an und wird zurück zu deiner App umgeleitet (konfiguriert als die Redirect-URI).
Bezüglich der umleitungsbasierten Anmeldung
  1. Dieser Authentifizierungsprozess folgt dem OpenID Connect (OIDC) Protokoll, und Logto erzwingt strenge Sicherheitsmaßnahmen, um die Benutzeranmeldung zu schützen.
  2. Wenn du mehrere Apps hast, kannst du denselben Identitätsanbieter (Logto) verwenden. Sobald sich der Benutzer bei einer App anmeldet, wird Logto den Anmeldeprozess automatisch abschließen, wenn der Benutzer auf eine andere App zugreift.

Um mehr über die Gründe und Vorteile der umleitungsbasierten Anmeldung zu erfahren, siehe Logto-Anmeldeerfahrung erklärt.


Redirect-URI konfigurieren

Wechseln wir zur Seite "Anwendungsdetails" der Logto-Konsole. Füge eine Redirect-URI http://localhost:3000/callback hinzu und klicke auf "Änderungen speichern".

Redirect-URI in Logto-Konsole

Anmelderoute implementieren

hinweis

Bevor du getSignInUrl() aufrufst, stelle sicher, dass du die Redirect-URI im Admin Console korrekt konfiguriert hast.

Erstelle eine Route in Express zur Anmeldung:

const { getSignInUrl } = require('./logto');

app.get('/sign-in', async (req, res) => {
const { redirectUri, codeVerifier, state, signInUri } = await getSignInUrl();
req.session.signIn = { codeVerifier, state, redirectUri };
res.redirect(signInUri);
});

und eine Route, um den Callback zu bearbeiten:

app.get('/callback', async (req, res) => {
if (!req.session.signIn) {
res.send('Bad request.');
return;
}

const response = await handleSignIn(
req.session.signIn,
`${req.protocol}://${req.get('host')}${req.originalUrl}`
);
req.session.tokens = {
...response,
expiresAt: response.expiresIn + Date.now(),
idToken: decodeIdToken(response.idToken),
};
req.session.signIn = null;

res.redirect('/');
});

Abmelden

TODO: Link zum Kapitel "Session & Cookies" im Benutzerhandbuch.

Du kannst Tokens in der Session löschen, um einen Benutzer von dieser Anwendung abzumelden. Und folge diesem Link, um mehr über "Abmelden" zu lesen.

app.get('/sign-out', (req, res) => {
req.session.tokens = null;
res.send('Abmeldung erfolgreich');
});

Auf geschützte Ressourcen zugreifen

Erstelle ein Middleware namens withAuth, um ein auth-Objekt an req anzuhängen und zu überprüfen, ob ein Benutzer angemeldet ist.

// auth.js

const { decodeIdToken } = require('@logto/js');
const { refreshTokens } = require('./logto');

const withAuth =
({ requireAuth } = { requireAuth: true }) =>
async (req, res, next) => {
if (requireAuth && !req.session.tokens) {
res.redirect('/sign-in');
return;
}

if (req.session.tokens) {
if (req.session.tokens.expiresAt >= Date.now()) {
// Zugangstoken abgelaufen, neue Tokens anfordern
try {
const response = await refreshTokens(req.session.tokens.refreshToken);
req.session.tokens = {
...response,
expiresAt: response.expiresIn + Date.now(),
idToken: decodeIdToken(response.idToken),
};
} catch {
// Austausch fehlgeschlagen, zur Anmeldung umleiten
res.redirect('/sign-in');
return;
}
}

req.auth = req.session.tokens.idToken.sub;
}

next();
};

module.exports = withAuth;

Erstelle eine index-Seite, zeige einen Anmeldelink für Gäste und einen Link zum Profil für bereits angemeldete Benutzer:

router.get('/', withAuth({ requireAuth: false }), function (req, res, next) {
res.render('index', { auth: Boolean(req.auth) });
});
extends layout

block content
h1 Hallo Logto
if auth
p: a(href="/user") Zum Profil gehen
else
p: a(href="/sign-in") Hier klicken, um sich anzumelden

Erstelle eine user-Seite, um userId (Subjekt) anzuzeigen:

app.get('/user', withAuth(), (req, res, next) => {
res.render('user', { userId: req.auth });
});
extends layout

block content
h1 Hallo Logto
p userId: #{userId}

Weiterführende Lektüre

Endbenutzerflüsse: Authentifizierungsflüsse, Kontoflüsse und Organisationsflüsse Connectors konfigurieren Schütze deine API