ZITADEL vs Keycloak vs Authentik : lequel choisir pour un B2B SaaS
La question revient systématiquement dès qu'une agence ou un développeur SaaS cherche à centraliser l'authentification de plusieurs clients : faut-il rester sur Keycloak, passer à Authentik, ou regarder ZITADEL ?
Keycloak est le standard historique, riche en fonctionnalités et supporté par Red Hat. Il couvre OIDC, SAML, LDAP, Kerberos, WebAuthn et dispose d'une documentation exhaustive. Son problème pour un VPS self-hosted : la JVM. Au repos, Keycloak consomme entre 512 Mo et 1 Go de RAM, et chaque realm est une arborescence de configuration distincte à maintenir. La semaine 38 de 2026 a illustré le risque : six CVEs corrigées en deux releases consécutives, dont deux DoS non authentifiés sur les endpoints SAML et de login.
Authentik (2026.8.2) est le choix naturel pour les équipes qui veulent un IdP complet avec une interface graphique, des flux visuels et du provisionnement SCIM. Il est écrit en Python/Django avec un frontend TypeScript, et sa stack complète demande au moins 1 Go de RAM utilisable. Le multi-tenant existe, mais s'implémente via des tenants séparés ou des groupes — pas via un modèle de données dédié.
ZITADEL (v2.x, Go) est le seul des trois à avoir conçu le multi-tenant comme primitif de première classe. Une installation unique expose des organisations indépendantes — chacune avec ses utilisateurs, ses IdP externes, ses politiques de mot de passe et ses applications. Pour une agence qui héberge dix clients sur un seul VPS, c'est la différence entre dix fichiers de configuration Keycloak et une API qui orchestre tout.
Pourquoi ZITADEL se distingue pour un usage agence ou B2B SaaS
- Multi-tenant natif : organisations et projets sont des primitives du modèle de données, pas des contournements — chaque client obtient son espace isolé sans duplication de configuration
- Écrit en Go : binaire unique, démarrage en quelques secondes, empreinte mémoire de 150 à 300 Mo stack complète contre 512 Mo à 1 Go de JVM pour Keycloak
- OIDC, OAuth2, SAML 2.0 et LDAP : tous les protocoles d'entreprise en une seule installation — pas besoin d'ajouter des extensions comme sur Keycloak
- Passkeys et WebAuthn natifs dès v2 : chaque organisation peut imposer des Passkeys comme seul facteur d'authentification, sans plugin ni configuration avancée
- API-first gRPC/REST : toute opération disponible dans la console l'est aussi via l'API — provisionner un tenant, créer une application OIDC, ou réinitialiser un MFA se script en quelques lignes
- Actions (webhooks/code) : logique personnalisée déclenchée lors des événements d'authentification (pre/post-auth, post-register) sans modifier le core
- Déploiement sans état applicatif : ZITADEL stocke tout dans PostgreSQL — pas de cache distribué, pas de volume supplémentaire, la scalabilité horizontale se réduit à démarrer une seconde instance
- Licence Apache 2.0 : le cœur est entièrement open source, sans fonctionnalité critique cachée derrière une licence Enterprise
Prérequis : serveur, Docker et DNS
ZITADEL est raisonnable en ressources pour un fournisseur d'identité de cette envergure. La configuration minimale recommandée par l'équipe pour un déploiement de production est 2 vCPU et 4 Go de RAM — dont environ 300 Mo seront utilisés par ZITADEL lui-même, le reste revenant à PostgreSQL et aux autres services de votre stack.
En pratique, sur un VPS dédié avec 4 Go de RAM, ZITADEL + PostgreSQL laissent largement de la place pour un reverse proxy nginx, un service de notification et quelques applications. Pour des tests ou un déploiement de développement, 2 Go suffisent.
Logiciels requis : Docker Engine 24+ et Docker Compose v2. Les ports 80 et 443 doivent être ouverts (nginx terminera le TLS). Un nom de domaine pointé vers l'IP de votre VPS — ZITADEL exige un FQDN même en développement, car les cookies OIDC sont liés au domaine. Si vous n'avez pas encore de certificat TLS, Let's Encrypt avec Certbot ou le plugin nginx suffisent.
Base de données : PostgreSQL 15 ou 16. ZITADEL supporte aussi CockroachDB pour les déploiements hautement disponibles, mais sur un VPS single-node, PostgreSQL est le choix naturel. La base doit être accessible depuis le conteneur ZITADEL — le réseau Docker bridge par défaut convient.
Stockage : ZITADEL ne stocke pas de fichiers sur disque (hors logs) — pas de volume de données dédié au-delà du volume PostgreSQL. Prévoir 10 Go pour la base de données en conditions normales d'utilisation.
Déployer ZITADEL avec Docker Compose et PostgreSQL
Créer l'arborescence de travail
Connectez-vous à votre VPS en SSH et créez les répertoires nécessaires :
mkdir -p /opt/zitadel/{config,certs} cd /opt/zitadelTous les fichiers de configuration et les secrets résideront ici.
Créer le fichier .env avec les secrets
Générez des secrets forts avant de les inscrire dans
.env:cat > /opt/zitadel/.env <<EOF POSTGRES_PASSWORD=$(openssl rand -base64 32 | tr -d '\n') ZITADEL_MASTERKEY=$(openssl rand -base64 32 | tr -d '\n' | head -c 32) ZITADEL_DOMAIN=sso.votre-domaine.com EOFLe
MASTERKEYdoit faire exactement 32 caractères — ZITADEL refuse de démarrer sans cela.Écrire le docker-compose.yml
Créez
/opt/zitadel/docker-compose.yml:services: db: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_USER: zitadel POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: zitadel volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U zitadel"] interval: 10s timeout: 5s retries: 5 zitadel: image: ghcr.io/zitadel/zitadel:stable restart: unless-stopped command: start-from-init --masterkey "${ZITADEL_MASTERKEY}" --tlsMode disabled environment: ZITADEL_DATABASE_POSTGRES_HOST: db ZITADEL_DATABASE_POSTGRES_PORT: 5432 ZITADEL_DATABASE_POSTGRES_DATABASE: zitadel ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: ${POSTGRES_PASSWORD} ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: zitadel ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: ${POSTGRES_PASSWORD} ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable ZITADEL_EXTERNALSECURE: "true" ZITADEL_EXTERNALDOMAIN: ${ZITADEL_DOMAIN} ZITADEL_EXTERNALPORT: 443 depends_on: db: condition: service_healthy ports: - "8080:8080" volumes: pgdata:Le flag
--tlsMode disableddélègue le TLS à nginx. ZITADEL écoute en HTTP sur le port 8080 en interne, nginx termine le TLS en amont.Configurer nginx comme reverse proxy TLS
Créez
/etc/nginx/sites-available/zitadel:server { listen 80; server_name sso.votre-domaine.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name sso.votre-domaine.com; ssl_certificate /etc/letsencrypt/live/sso.votre-domaine.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/sso.votre-domaine.com/privkey.pem; # ZITADEL utilise gRPC-Web — http2 est requis location / { grpc_pass grpc://127.0.0.1:8080; proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }Activez le site et obtenez le certificat :
ln -s /etc/nginx/sites-available/zitadel /etc/nginx/sites-enabled/ certbot --nginx -d sso.votre-domaine.com nginx -t && systemctl reload nginxDémarrer la stack ZITADEL
cd /opt/zitadel docker compose up -d docker compose logs -f zitadelLe premier démarrage (
start-from-init) initialise la base de données, applique les migrations et crée l'organisation par défaut. Attendez le messageserver is listening on [::]:8080dans les logs — cela prend généralement 30 à 60 secondes.Récupérer les credentials admin initiaux
Lors du premier démarrage avec
start-from-init, ZITADEL génère un compte admin et un fichier de clé de service. Récupérez-les avec :docker compose logs zitadel 2>&1 | grep -A 5 'initial'Les credentials s'affichent une seule fois dans les logs. Notez le login et le mot de passe — ils ne peuvent pas être récupérés après. Si vous les perdez, relancez avec
start-from-initaprès avoir vidé la base.Se connecter à la console d'administration
Ouvrez
https://sso.votre-domaine.com/ui/consoledans un navigateur. Connectez-vous avec les credentials récupérés à l'étape précédente. Vous arrivez sur le tableau de bord de l'organisation par défaut — c'est votre organisation principale (votre propre compte ou votre agence). Depuis ici, vous pouvez créer des organisations clients, des projets et des applications OIDC.Créer votre première organisation client
Dans la console, allez dans Organisation → Nouvelle organisation. Donnez-lui le nom de votre client (ex.
client-acme). ZITADEL crée automatiquement un espace isolé : les utilisateurs de cette organisation ne voient pas ceux des autres, et les politiques de mot de passe peuvent être différentes. Vous pouvez ensuite inviter un administrateur de l'organisation client — il aura accès uniquement à son espace.Créer un projet et une application OIDC
Dans l'organisation, allez dans Projets → Nouveau projet. Un projet regroupe une ou plusieurs applications. Créez ensuite une Application web de type OIDC. ZITADEL vous demande le type de client (Web, SPA, Native) et les URLs de redirection. Il génère un
Client IDet unClient Secret. Notez le Discovery URL :https://sso.votre-domaine.com/.well-known/openid-configuration— toutes vos applications configurées en OIDC pointent vers cet endpoint.Vérifier l'état et activer les mises à jour automatiques
Vérifiez que tout fonctionne :
curl -s https://sso.votre-domaine.com/healthz | jq .Une réponse
{"status": "pass"}confirme que ZITADEL répond correctement. Pour les mises à jour, remplacez le tagstablepar le numéro de version exact dansdocker-compose.ymlet fixez-le —stablesuit le dernier release, ce qui peut introduire des changements non planifiés en production.
Configurer l'authentification OIDC pour vos applications
Une fois ZITADEL démarré et une application OIDC créée, la configuration côté application est standard — n'importe quelle bibliothèque OIDC (passport.js, next-auth, python-oidc, etc.) fonctionne sans adaptation spécifique à ZITADEL.
Les paramètres OIDC essentiels à fournir à votre application :
ISSUER=https://sso.votre-domaine.com
CLIENT_ID=<client_id_généré>
CLIENT_SECRET=<client_secret_généré> # seulement pour les apps Web (pas les SPAs)
REDIRECT_URI=https://votre-app.com/callback
SCOPES=openid profile emailLe Discovery Document est disponible à https://sso.votre-domaine.com/.well-known/openid-configuration. Il expose automatiquement tous les endpoints : authorization_endpoint, token_endpoint, userinfo_endpoint, jwks_uri. La plupart des bibliothèques OIDC modernes peuvent se configurer depuis ce seul URL de découverte.
Exemple avec next-auth (Next.js) :
// pages/api/auth/[...nextauth].js
import NextAuth from 'next-auth'
export default NextAuth({
providers: [
{
id: 'zitadel',
name: 'ZITADEL',
type: 'oauth',
wellKnown: 'https://sso.votre-domaine.com/.well-known/openid-configuration',
clientId: process.env.ZITADEL_CLIENT_ID,
clientSecret: process.env.ZITADEL_CLIENT_SECRET,
authorization: { params: { scope: 'openid profile email' } },
idToken: true,
checks: ['pkce', 'state'],
profile(profile) {
return { id: profile.sub, name: profile.name, email: profile.email }
}
}
]
})Pour une SPA (React, Vue), utilisez le flow PKCE sans secret côté client. ZITADEL gère la rotation des codes PKCE nativement — pas de configuration supplémentaire requise.
Modèle multi-tenant : organisations, projets et applications
Le modèle de données ZITADEL s'articule en trois niveaux qui correspondent exactement aux besoins d'une agence ou d'un SaaS B2B.
L'organisation est le conteneur d'un tenant. Chaque organisation a ses propres utilisateurs, ses propres IdP externes (Google, GitHub, SAML d'entreprise) et ses propres politiques de complexité de mot de passe. Elle peut être administrée par un délégué de l'organisation — votre client — sans que cela lui donne accès aux autres organisations.
Le projet regroupe les applications d'un même produit. Un projet peut contenir une application web, une application mobile et une API — toutes partageant les mêmes rôles et permissions, définis une seule fois au niveau du projet. Les tokens JWT émis par ZITADEL portent les rôles du projet dans le claim urn:zitadel:iam:org:project:roles.
L'application représente un client OIDC, OAuth2 ou SAML spécifique. Elle définit les URLs de redirection, le type de client et les scopes autorisés.
Ce modèle permet à une agence de gérer dix clients sur une seule installation ZITADEL sans aucun risque de contamination entre tenants. Comparez avec Keycloak où chaque realm est une configuration complète à maintenir séparément, ou avec Authentik où le multi-tenant s'implémente via des tenants distincts partageant le même espace de noms.
Utiliser l'API pour automatiser : ZITADEL expose une API gRPC/REST complète. Créer une organisation et ses premières applications depuis un script d'onboarding prend une vingtaine de lignes :
# Créer une organisation via l'API REST
curl -X POST https://sso.votre-domaine.com/v1/orgs \
-H 'Authorization: Bearer <admin_token>' \
-H 'Content-Type: application/json' \
-d '{"name": "client-acme", "domain": "acme.example.com"}'L'API renvoie l'ID de l'organisation, que vous passez ensuite aux appels de création de projet et d'application.
ZITADEL vs Authentik vs Keycloak — comparatif technique
Faites défiler le tableau
| Critère | ZITADEL v2.x | Authentik 2026.8 | Keycloak 26.7 |
|---|---|---|---|
| Langage / runtime | Go — binaire unique | Python + Rust (frontend) | Java (JVM) |
| RAM stack complète (repos) | 150 – 300 Mo | ~1 Go (worker + PostgreSQL) | 512 Mo – 1 Go (JVM seule) |
| Multi-tenant natif | Oui — Organisations primitives | Partiel — tenants séparés | Partiel — realms séparés |
| OIDC / OAuth2 / SAML | Oui — les trois natifs | Oui — les trois natifs | Oui — les trois natifs |
| Passkeys / WebAuthn | Oui, natif v2 | Oui (2026.2+) | Oui (v21+) |
| API-first | gRPC + REST, 100 % coverage | REST partielle | REST partielle |
| Interface admin | Console web + API | UI complète avec flux visuels | UI complète, configuration XML |
| Licence | Apache 2.0 (core) | MIT (Community) / Enterprise | Apache 2.0 |
Activer les Passkeys et le MFA dans ZITADEL v2
ZITADEL v2 propose les Passkeys WebAuthn comme facteur d'authentification principal — pas seulement comme second facteur. Pour les activer dans une organisation :
1. Dans la console, ouvrez Organisation → Politique de sécurité → Politique MFA.
2. Activez Passkeys (WebAuthn sans mot de passe) et sélectionnez Forcer si vous souhaitez que tous les utilisateurs de cette organisation utilisent uniquement des Passkeys.
3. Pour une politique plus souple : autorisez à la fois les Passkeys et le TOTP — l'utilisateur choisit lors de l'enrôlement.
Pour imposer le MFA par défaut à tous les nouveaux utilisateurs d'un projet :
- Ouvrez le Projet → Paramètres → Forcer le MFA — cette option oblige chaque utilisateur de ce projet à enregistrer un second facteur avant d'accéder à l'application.
Les Passkeys sont stockés côté navigateur (ou sur clé de sécurité FIDO2) et jamais transmis à ZITADEL — seule la preuve cryptographique est échangée. C'est particulièrement adapté aux applications B2B où les comptes partagés entre collègues sont courants : chaque appareil enregistre sa propre Passkey, sans mot de passe à partager.
TOTP avec Google Authenticator ou Bitwarden Authenticator : ZITADEL génère un QR code standard — n'importe quelle application TOTP compatible RFC 6238 fonctionne. Pour les déploiements entreprise, ZITADEL supporte aussi les clés de sécurité U2F/FIDO2 physiques (YubiKey, etc.).
Dépannage : erreurs fréquentes avec ZITADEL sous Docker
masterkey must be 32 characters : le ZITADEL_MASTERKEY généré par openssl rand -base64 32 produit une chaîne base64 d'une longueur variable selon le padding. Utilisez head -c 32 pour tronquer à exactement 32 caractères, ou une commande qui génère directement 32 octets aléatoires en hexadécimal : openssl rand -hex 16 (32 caractères hex = 16 octets).
connection refused sur le port 8080 alors que le conteneur tourne : ZITADEL ne répond sur :8080 que lorsque les migrations sont terminées — le start-from-init sur une base vide peut prendre 30 à 90 secondes. Attendez le log server is listening on [::]:8080 avant de tester.
Boucle de redirection HTTP/HTTPS : ZITADEL génère ses redirects OAuth2 en HTTPS dès que ZITADEL_EXTERNALSECURE=true. Si nginx n'envoie pas correctement l'en-tête X-Forwarded-Proto: https, ZITADEL croit être derrière HTTP et génère des redirects cassés. Vérifiez que proxy_set_header X-Forwarded-Proto https est bien dans le bloc location / de nginx.
invalid_client lors du flux OIDC : vérifiez que l'URL de redirection déclarée dans l'application ZITADEL correspond exactement à celle envoyée par votre application — ZITADEL est strict sur la correspondance (pas de trailing slash, pas de paramètre supplémentaire). Activez les logs de débogage avec ZITADEL_LOG_LEVEL=debug pour voir le détail des validations.
PostgreSQL refuse la connexion : le conteneur ZITADEL démarre avant que PostgreSQL soit prêt à accepter des connexions, même avec depends_on: condition: service_healthy. Si cela se produit, relancez simplement ZITADEL : docker compose restart zitadel. En production, utilisez une politique de restart (restart: on-failure:5) pour absorber les délais de démarrage.
Perte des credentials admin initiaux : les credentials ne s'affichent qu'une seule fois au premier démarrage. Si vous les perdez, supprimez la base de données PostgreSQL (docker compose down -v) et relancez start-from-init. Sur un VPS de production, créez immédiatement un second compte admin et notez les credentials dans votre gestionnaire de secrets.
Prochaine étape : approfondir votre stack SSO self-hosted
ZITADEL en production sur un VPS est un excellent premier pas. Pour aller plus loin :
- [Déployer ZITADEL sur VPS — guide avancé](/deployer-zitadel-vps) : certificats TLS automatiques avec Let's Encrypt, backup PostgreSQL avec pg_dump vers S3, et mise à jour sans downtime.
- [Authentik vs Authelia : quel SSO self-hosted pour votre VPS ?](/authentik-vs-authelia-sso-self-hosted) : si votre cas d'usage est moins orienté multi-tenant et plus orienté proxy d'authentification, la comparaison des deux alternatives les plus légères.
- [Migrer de Keycloak vers Authelia ou ZITADEL](/keycloak-cves-semaine-38-migrer-authelia-zitadel-vps) : les six CVEs de la semaine 38 analysées, et le chemin de migration concret depuis un realm Keycloak existant.
- [Héberger Authelia sur VPS](/self-host-authelia-vps) : si vous cherchez une passerelle d'authentification ultra-légère (moins de 30 Mo) plutôt qu'un IdP complet, Authelia reste la référence.
Un VPS avec 4 Go de RAM, accès root et IPv4 dédiée couvre confortablement ZITADEL en production avec de la marge pour les applications qu'il protège.