Vue + Zitadel — Lernprojekt

Kleine Vue-App, die Login/Logout über Zitadel (OIDC) zeigt — ohne eigenes Passwort-Formular.

Stack: Vue 3, TypeScript, Vite
Zitadel-Instanz: https://auth.schild.work
Kernkomponente: src/components/auth-area.vue

pnpm install
pnpm dev

Wie funktioniert der Login?

Die App nutzt OpenID Connect (OIDC) mit Authorization Code + PKCE — der Standard für SPAs.

Vue App                    Zitadel
   │  1. Sign in/up         │
   │ ─────────────────────►│  Login- oder Register-Seite
   │  2. Redirect mit code  │
   │ ◄─────────────────────│  ?code=... auf localhost:5173
   │  3. POST /oauth/v2/token│
   │ ─────────────────────►│  code + code_verifier (PKCE)
   │  4. access_token        │
   │ ◄─────────────────────│  (+ id_token, optional refresh_token)

Sign in → normale Login-Seite von Zitadel.
Sign up → gleicher Flow, aber mit prompt=create (direkt Registrierung).

Credentials (E-Mail/Passwort) werden nur auf Zitadel eingegeben, nie in der Vue-App.

Logout → Redirect zu Zitadel end_session, danach zurück auf die App.


Wie ist das implementiert?

Alles in auth-area.vue:

Schritt Was passiert
Sign in / Sign up redirectToAuthorize() — PKCE-Verifier erzeugen, Redirect zu /oauth/v2/authorize
Zurück auf der App handleCallback() liest ?code= aus der URL
Token holen exchangeCodeForTokens() — POST an /oauth/v2/token
Session speichern access_token und id_token in sessionStorage
App-Start loadStoredSession() — Token aus sessionStorage laden

Konfiguration oben in der Datei:

const ZITADEL_ISSUER = "https://auth.schild.work";
const CLIENT_ID = "…";           // aus Zitadel Console
const REDIRECT_URI = "http://localhost:5173/";
const POST_LOGOUT_REDIRECT_URI = "http://localhost:5173/";
const SCOPES = "openid profile email offline_access";

In Zitadel: Application (User Agent / SPA, PKCE), Redirect URIs und Post Logout Redirect URI eintragen, Register allowed für Sign up aktivieren.


Woran erkennt man, dass man eingeloggt ist?

In der App: isAuthenticated — ein computed, das prüft ob accessToken gesetzt ist.

const isAuthenticated = computed(() => accessToken.value !== null);

Im Template: eingeloggt → Name/E-Mail + Logout; nicht eingeloggt → Sign in / Sign up.

User-Anzeige: Der id_token ist ein JWT. Payload decodieren (Mitte zwischen den Punkten) liefert z. B. email, name — siehe userLabel in der Komponente.

Technisch: Ein gültiger access_token in sessionStorage (zitadel_access_token) bedeutet: Session vorhanden. Nach Tab-Schließen weg (sessionStorage), nach erneutem Öffnen neu einloggen.


API-Aufrufe mit Login

Geschützte APIs erwarten den Token im Header:

const token = sessionStorage.getItem("zitadel_access_token");

const response = await fetch("https://api.example.com/me", {
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

Die API (nicht Vue) muss das JWT prüfen — Signatur gegen Zitadel JWKS (/oauth/v2/keys), Issuer https://auth.schild.work, Ablaufzeit (exp).

Token Zweck
access_token API-Aufrufe (Authorization: Bearer …)
id_token User-Infos in der UI (JWT decodieren)
refresh_token Neues access_token ohne erneuten Login (Scope offline_access)

Wichtig: Token nur an eigene, vertrauenswürdige APIs senden. Die API validiert das Token serverseitig — die Vue-App allein „macht“ keine Sicherheit.


Was dieses Projekt nicht macht

  • Kein eigenes Registrierungsformular (E-Mail/Passwort direkt an Zitadel) — dafür bräuchte man ein Backend mit Service-User-Token.
  • Kein Refresh-Token-Handling — könnte später ergänzt werden.
S
Description
No description provided
Readme
45 KiB
Languages
Vue 90.2%
HTML 5.8%
TypeScript 4%