Anmeldung zu einer React-Single-Page-App hinzufügen
Dieser Quickstart fügt einer mit Vite erstellten React-Single-Page-App die KeenAuth-Anmeldung hinzu und verwendet dafür das Browser-SDK @keenauth/js. Die App meldet Benutzer per Weiterleitung an, ruft eine geschützte API mit einem DPoP-gebundenen Zugriffstoken auf und meldet Benutzer wieder ab. Sie testen sie außerdem offline. Das SDK wird in JavaScript-Browser-SDK beschrieben.
In einer Single-Page-App liegen die Tokens im Browser. Das SDK hält sie nur im Speicher und bindet sie mit DPoP an einen nicht exportierbaren Schlüssel, sodass ein gestohlenes Token ohne diesen Schlüssel nicht verwendet werden kann. Wenn Ihre App einen Server hat, bewahrt der Next.js-Quickstart alle Tokens stattdessen auf dem Server auf (Backend for Frontend), was die stärkere Wahl ist.
Die App hat folgende Pfade:
| Pfad | Was er tut |
|---|---|
/ |
Eine Schaltfläche zum Anmelden bzw. der Benutzer, eine Schaltfläche, die Ihre API aufruft, und eine Schaltfläche zum Abmelden |
/callback |
Die Redirect-URI. Das SDK schließt die Anmeldung hier ab, und die App kehrt zu der Seite zurück, auf der der Benutzer begonnen hat |
Voraussetzungen
- Eine KeenAuth-Umgebung. Ihr Issuer sieht etwa so aus:
https://your-environment.eu.keenauth.app. - Eine Single-Page-Anwendung in der Umgebung (Anwendung anlegen) mit
app_typespaundtoken_endpoint_auth_methodnone(ein Public Client hat kein Secret). In einerdevelopment-Umgebung registrieren Sie:- die Redirect-URI
http://localhost/callback(eine Loopback-Redirect-URI wird ohne Port registriert und passt auf jeden Port); - die Post-Logout-Redirect-URI
http://localhost/; - den erlaubten Web-Origin
http://localhost:5173, damit der Browser den Token-Endpunkt aufrufen darf.
- die Redirect-URI
- Eine API, die in der Umgebung registriert ist (API anlegen) mit dem Scope
messages:read. Ihridentifier, zum Beispielhttps://api.example.com, ist die Audience ihrer Zugriffstoken. Der Express-Quickstart baut eine API, die diese Tokens akzeptiert. - Node.js 22.18 oder neuer.
1. Installieren
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install @keenauth/js
Das SDK ist noch nicht veröffentlicht. Bis dahin verlinkt das Beispiel auf den SDK-Quellcode in seiner package.json. Das Beispiel fixiert vite@8.3.4, @vitejs/plugin-react@6.1.2, react@19.3.1 und react-dom@19.3.1.
2. Konfigurieren
Die App liest ihre Konfiguration ausschließlich aus Umgebungsvariablen. Legen Sie sie in .env.local ab, die Vite lädt, und committen Sie diese Datei niemals. Vite bettet jede VITE_-Variable in das JavaScript ein, das der Browser herunterlädt. Hinterlegen Sie dort daher kein Secret. Diese App hat keins.
| Variable | Pflicht | Bedeutung |
|---|---|---|
VITE_KEENAUTH_ISSUER |
ja | Der Issuer Ihrer Umgebung |
VITE_KEENAUTH_CLIENT_ID |
ja | Die client_id der Single-Page-Anwendung |
VITE_KEENAUTH_API_AUDIENCE |
ja | Der Identifier Ihrer API in KeenAuth |
VITE_API_URL |
nein | Basis-URL Ihrer API. Leer bedeutet der eigene Origin der App |
API_PROXY_TARGET |
nein | Nur für die Entwicklung: Ziel, an das der Dev-Server /api weiterleitet; Standard http://localhost:8080 |
loadConfig liest diese Werte und prüft sie. Fehlt einer, bricht es die App mit einem Fehler ab, der die Variable nennt:
export function loadConfig(env: Readonly<Record<string, unknown>>): AppConfig {
const need = (name: string): string => {
const v = env[name];
if (typeof v !== "string" || v.trim() === "") throw new Error(`${name} is required (see .env.example)`);
return v.trim();
};
3. SDK-Client erstellen
export function createAuth(config: AppConfig, origin: string, inject: Injectables = {}): KeenAuth {
return new KeenAuth({
issuer: config.issuer,
clientId: config.clientId,
redirectUri: `${origin}${CALLBACK_PATH}`,
postLogoutRedirectUri: `${origin}/`,
// The access token is for this API: its aud is the API identifier (RFC 8707).
resource: config.apiAudience,
// messages:read is a scope of that API; the example API requires it on /api/messages.
scope: "openid profile email offline_access messages:read",
...inject,
});
}
resource ist der Identifier Ihrer API (RFC 8707). Das SDK sendet ihn bei der Autorisierungsanfrage, beim Austausch des Codes und bei jedem Refresh, und die Audience des Zugriffstokens ist Ihre API. Ohne ihn ist das Zugriffstoken nicht für Ihre API bestimmt, und die API weist es zurück. Das SDK prüft jede Option beim Erstellen des Clients. Ein ungültiger Issuer oder resource löst einen KeenAuthError mit dem Code config_invalid aus.
Erstellen Sie einen Client für die Seite und übergeben Sie ihn an einen Provider:
const config = loadConfig(import.meta.env);
const auth = createAuth(config, window.location.origin);
createRoot(document.getElementById("root")!).render(
<StrictMode>
<AuthProvider auth={auth}>
<App apiUrl={config.apiUrl} />
</AuthProvider>
</StrictMode>,
);
4. Anmelden und den Callback verarbeiten
Die Anmeldeschaltfläche startet die Anmeldung per Weiterleitung: Authorization-Code-Flow mit PKCE (S256), state und nonce. appState ist der Pfad, zu dem zurückgekehrt wird.
<button onClick={() => auth.signInRedirect({ appState: window.location.pathname })}>Sign in</button>
Wenn KeenAuth zu /callback zurückleitet, schließt der Provider die Anmeldung ab. Das SDK prüft state, iss und das ID-Token und tauscht den Code mit einem DPoP-Proof aus. Eine Autorisierungsantwort kann nur einmal verwendet werden, und Reacts StrictMode führt Effekte in der Entwicklung zweimal aus. Deshalb merkt sich completeSignIn das erste Ergebnis:
export function completeSignIn(auth: KeenAuth, href: string): Promise<string | null> {
const url = new URL(href);
if (url.pathname !== CALLBACK_PATH || !(url.searchParams.has("code") || url.searchParams.has("error"))) {
return Promise.resolve(null);
}
let done = completions.get(auth);
if (!done) {
done = auth.handleRedirectCallback(href).then((r) => safeReturnPath(r.appState));
completions.set(auth, done);
}
return done;
}
Anschließend entfernt der Provider code und state aus der Adresszeile. Als Rückkehrpfad wird nur ein Pfad innerhalb Ihrer App verwendet, damit ein manipuliertes appState den Benutzer nicht auf eine andere Website schicken kann:
export function safeReturnPath(path: string | undefined): string {
return path !== undefined && /^\/(?![/\\])/.test(path) ? path : "/";
}
Liefern Sie index.html für /callback auch in der Produktion aus (der Vite-Dev-Server tut dies bereits).
5. Ihre API aufrufen
authorizedFetch sendet Authorization: DPoP <token> mit einem frischen, an das Token gebundenen Proof und erneuert das Token, bevor es abläuft:
export async function fetchMessages(auth: KeenAuth, apiUrl: string): Promise<unknown> {
const res = await auth.authorizedFetch(`${apiUrl}/api/messages`, { headers: { Accept: "application/json" } });
if (!res.ok) throw new Error(`The API answered ${res.status}`);
return res.json();
}
In der Entwicklung leitet der Vite-Dev-Server /api an Ihre API weiter. Der Browser ruft die API somit auf dem eigenen Origin der App auf, und die API benötigt kein CORS:
proxy: { "/api": env.API_PROXY_TARGET || "http://localhost:8080" },
Die API prüft den DPoP-Proof gegen die URL, die der Browser aufgerufen hat. Wenn Sie die API des Express-Quickstarts hinter diesem Proxy betreiben, setzen Sie ihre PUBLIC_BASE_URL auf http://localhost:5173. In der Produktion stellen Sie die API auf demselben Origin bereit oder aktivieren CORS dafür für den Origin Ihrer App und setzen VITE_API_URL.
6. Abmelden
<button onClick={() => auth.signOut()}>Sign out</button>
signOut verwirft die Tokens, widerruft das Refresh-Token und schickt den Browser zum Logout-Endpunkt Ihrer Umgebung, der ihn zu / zurückleitet.
7. Die App starten
Starten Sie die API aus dem Express-Quickstart mit PUBLIC_BASE_URL=http://localhost:5173 und KEENAUTH_AUDIENCE auf den Identifier Ihrer API gesetzt, und starten Sie dann die App:
cp .env.example .env.local # then fill it in
npm run dev
Öffnen Sie http://localhost:5173, wählen Sie Sign in, melden Sie sich bei KeenAuth an und wählen Sie Call the API.
Das SDK behält Tokens nur im Speicher. Nach einem Neuladen der Seite ist der Benutzer in der App abgemeldet und meldet sich erneut an. Die KeenAuth-Sitzung sorgt meist dafür, dass dies eine Weiterleitung ohne Abfrage ist.
8. Ohne Netzwerk testen
Die Tests benötigen weder ein Netzwerk noch ein KeenAuth-Konto. Ein gefälschtes fetch übernimmt die Rolle des Autorisierungsservers und der API, und das ID-Token wird mit einem eigens für den Test erzeugten Schlüssel signiert. Node führt die TypeScript-Tests direkt aus.
assert.deepEqual(await fetchMessages(s.auth, ""), { messages: ["hello"] });
const call = s.seen.find((c) => c.url === `${ORIGIN}/api/messages`)!;
assert.equal(call.headers.get("Authorization"), "DPoP access-token-1");
npm test
npm run typecheck
npm run deps # pinned versions, lockfile sources and licences
npm run build
Die Tests prüfen, dass die Konfiguration validiert wird, dass das SDK einen ungültigen Issuer oder resource ablehnt, dass die Anmeldung PKCE S256 und Ihre API als resource sendet, dass der Callback genau einmal abgeschlossen wird und resource zusammen mit dem Code sendet, dass ein gefälschtes state vor jeder Token-Anfrage abgelehnt wird, dass nach der Anmeldung nur ein Pfad innerhalb der App verwendet wird, dass kein Token in den Speicher geschrieben wird und dass der API-Aufruf das DPoP-gebundene Token und seinen Proof enthält.
Die vollständige Anmeldung mit einem echten Browser benötigt eine KeenAuth-Umgebung. Sie ist nicht Teil der Offline-Tests.
Nächste Schritte
- JavaScript-Browser-SDK: alle Optionen, Speicherort der Tokens und die aktuellen Einschränkungen.
- React-SDK: eingebettete Komponenten für Anmeldung, Registrierung und Konto.
- Express-API absichern: die API, die diese App aufruft.