Implémenter le flux OIDC Authorization Code + PKCE complet avec Zitadel dans une app SPA en 2026.
Voir notre guide Zitadel.
Configurer l’application
- Console Zitadel → Project → Add Application
- Type : User Agent (SPA)
- Auth method : PKCE
- Redirect URIs :
https://app.exemple.sn/auth/callback - Post Logout URIs :
https://app.exemple.sn/ - Récupérer Client ID
Frontend JS avec oidc-client-ts
// npm install oidc-client-ts
import { UserManager } from "oidc-client-ts";
const userManager = new UserManager({
authority: "https://auth.exemple.sn",
client_id: "VOTRE_CLIENT_ID",
redirect_uri: "https://app.exemple.sn/auth/callback",
post_logout_redirect_uri: "https://app.exemple.sn/",
response_type: "code",
scope: "openid profile email offline_access",
});
// Login
async function login() {
await userManager.signinRedirect();
}
// Callback handler (page /auth/callback)
async function handleCallback() {
const user = await userManager.signinRedirectCallback();
console.log("Connecté :", user.profile);
// user.access_token pour appeler vos API
window.location.href = "/dashboard";
}
// Récupérer user en cours
async function getUser() {
const user = await userManager.getUser();
return user;
}
// Logout
async function logout() {
await userManager.signoutRedirect();
}
Backend : valider le token
// Node.js avec jose
import * as jose from "jose";
const JWKS = jose.createRemoteJWKSet(
new URL("https://auth.exemple.sn/oauth/v2/keys")
);
async function validateToken(token: string) {
const { payload } = await jose.jwtVerify(token, JWKS, {
issuer: "https://auth.exemple.sn",
audience: "VOTRE_CLIENT_ID",
});
return payload;
}
// Middleware Hono
app.use("/api/*", async (c, next) => {
const auth = c.req.header("Authorization");
if (!auth?.startsWith("Bearer ")) return c.json({error: "Unauthorized"}, 401);
try {
const claims = await validateToken(auth.substring(7));
c.set("user", claims);
await next();
} catch (e) {
return c.json({error: "Invalid token"}, 401);
}
});
Refresh token
oidc-client-ts gère automatiquement le refresh des tokens si vous demandez le scope offline_access. Activez aussi « Silent renew » dans la config UserManager pour rafraîchir avant expiration.