Aggiungi l'accesso a un'applicazione React single-page
Questa quickstart aggiunge l'accesso con KeenAuth a un'applicazione React single-page creata con Vite, usando il browser SDK @keenauth/js. L'app accede gli utenti con un redirect, chiama un'API protetta con un access token vincolato tramite DPoP e disconnette gli utenti. Puoi testarla anche offline. L'SDK è descritto in SDK browser JavaScript.
In una single-page app i token vivono nel browser. L'SDK li mantiene solo in memoria e li lega a una chiave non estraibile con DPoP, quindi un token rubato non può essere usato senza quella chiave. Se la tua app ha un server, la Quickstart Next.js mantiene invece ogni token sul server (backend for frontend), che è la scelta più robusta.
L'app ha questi percorsi:
| Percorso | Cosa fa |
|---|---|
/ |
Un pulsante di accesso oppure l'utente, un pulsante che chiama la tua API e un pulsante di disconnessione |
/callback |
Il redirect URI. L'SDK completa qui l'accesso e l'app torna alla pagina da cui l'utente è partito |
Prima di iniziare
- Un ambiente KeenAuth. Il suo issuer ha la forma
https://your-environment.eu.keenauth.app. - Un'applicazione single-page nell'ambiente (Crea un'applicazione) con
app_typespaetoken_endpoint_auth_methodnone(un client pubblico non ha segreto). In un ambientedevelopment, registra:- il redirect URI
http://localhost/callback(un redirect URI loopback viene registrato senza porta e corrisponde a qualsiasi porta); - il post-logout redirect URI
http://localhost/; - l'origine web consentita
http://localhost:5173, così il browser può chiamare l'endpoint dei token.
- il redirect URI
- Un'API registrata nell'ambiente (Crea un'API) con lo scope
messages:read. Il suoidentifier, per esempiohttps://api.example.com, è l'audience dei suoi access token. La Quickstart Express crea un'API che accetta questi token. - Node.js 22.18 o successivo.
1. Installa
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install @keenauth/js
L'SDK non è ancora pubblicato. Finché non lo sarà, l'esempio collega il sorgente dell'SDK nel suo package.json. L'esempio fissa vite@8.3.4, @vitejs/plugin-react@6.1.2, react@19.3.1 e react-dom@19.3.1.
2. Configura
L'app legge la configurazione solo da variabili d'ambiente. Mettile in .env.local, che Vite carica, e non committare mai quel file. Vite incorpora ogni variabile VITE_ nel JavaScript che il browser scarica, quindi non mettere lì nessun segreto. Questa app non ne ha.
| Variabile | Obbligatoria | Significato |
|---|---|---|
VITE_KEENAUTH_ISSUER |
sì | L'issuer del tuo ambiente |
VITE_KEENAUTH_CLIENT_ID |
sì | Il client_id dell'applicazione single-page |
VITE_KEENAUTH_API_AUDIENCE |
sì | L'identificatore della tua API in KeenAuth |
VITE_API_URL |
no | URL base della tua API. Vuoto significa l'origine dell'app |
API_PROXY_TARGET |
no | Solo sviluppo: dove il dev server inoltra /api; predefinito http://localhost:8080 |
loadConfig legge e verifica questi valori. Se una variabile manca, ferma l'app con un errore che ne indica il nome:
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. Crea il client SDK
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 è l'identificatore della tua API (RFC 8707). L'SDK lo invia nella richiesta di autorizzazione, nello scambio del codice e in ogni refresh, e l'audience dell'access token è la tua API. Senza, l'access token non è destinato alla tua API e l'API lo rifiuta. L'SDK verifica ogni opzione alla creazione del client; un issuer o un resource non validi lanciano un KeenAuthError con codice config_invalid.
Crea un solo client per la pagina e passalo a un 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. Accedi e gestisci il callback
Il pulsante di accesso avvia l'accesso con redirect: authorization code flow con PKCE (S256), state e nonce. appState è il percorso a cui tornare.
<button onClick={() => auth.signInRedirect({ appState: window.location.pathname })}>Sign in</button>
Quando KeenAuth reindirizza a /callback, il provider completa l'accesso. L'SDK verifica state, iss e l'ID token, e scambia il codice con una prova DPoP. Una risposta di autorizzazione può essere usata una sola volta, e StrictMode di React esegue due volte gli effect in sviluppo, quindi completeSignIn ricorda il primo risultato:
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;
}
Il provider poi rimuove code e state dalla barra degli indirizzi. Come percorso di ritorno viene usato solo un percorso interno alla tua app, così un appState costruito ad arte non può mandare l'utente su un altro sito:
export function safeReturnPath(path: string | undefined): string {
return path !== undefined && /^\/(?![/\\])/.test(path) ? path : "/";
}
Servi index.html per /callback anche in produzione (il dev server di Vite lo fa già).
5. Chiama la tua API
authorizedFetch invia Authorization: DPoP <token> con una prova nuova legata al token, e rinnova il token quando sta per scadere:
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 sviluppo, il dev server di Vite inoltra /api alla tua API, così il browser la chiama sull'origine dell'app e l'API non ha bisogno di CORS:
proxy: { "/api": env.API_PROXY_TARGET || "http://localhost:8080" },
L'API verifica la prova DPoP rispetto all'URL che il browser ha chiamato. Quando esegui l'API della Quickstart Express dietro questo proxy, imposta il suo PUBLIC_BASE_URL a http://localhost:5173. In produzione, servi l'API sulla stessa origine oppure abilita CORS per l'origine della tua app, e imposta VITE_API_URL.
6. Disconnettiti
<button onClick={() => auth.signOut()}>Sign out</button>
signOut elimina i token, revoca il refresh token e invia il browser all'endpoint di logout del tuo ambiente, che lo riporta a /.
7. Avvia l'app
Avvia l'API della Quickstart Express con PUBLIC_BASE_URL=http://localhost:5173 e KEENAUTH_AUDIENCE impostato all'identificatore della tua API, poi avvia l'app:
cp .env.example .env.local # then fill it in
npm run dev
Apri http://localhost:5173, scegli Sign in, accedi su KeenAuth e scegli Call the API.
L'SDK mantiene i token solo in memoria. Dopo un ricaricamento della pagina l'utente risulta disconnesso nell'app e deve accedere di nuovo; la sessione KeenAuth di solito rende questo un redirect senza richiesta di credenziali.
8. Testa senza rete
I test non richiedono rete né un account KeenAuth. Un fetch finto fa da authorization server e da API, e l'ID token è firmato con una chiave creata per il test. Node esegue direttamente i test TypeScript.
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
I test verificano che la configurazione venga validata, che l'SDK rifiuti un issuer o un resource non validi, che l'accesso invii PKCE S256 e la tua API come resource, che il callback venga completato una sola volta e invii resource con il codice, che uno state falsificato venga rifiutato prima di qualsiasi richiesta di token, che dopo l'accesso venga usato solo un percorso interno all'app, che nessun token venga scritto nello storage e che la chiamata API porti il token vincolato a DPoP e la sua prova.
L'accesso completo con un browser reale richiede un ambiente KeenAuth. Non fa parte dei test offline.
Passi successivi
- SDK browser JavaScript: ogni opzione, dove vengono conservati i token e i limiti attuali.
- SDK React: componenti incorporati per accesso, iscrizione e account.
- Proteggi un'API Express: l'API che questa app chiama.