113 lines
4.0 KiB
Markdown
113 lines
4.0 KiB
Markdown
# 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`
|
||
|
||
```bash
|
||
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:
|
||
|
||
```ts
|
||
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.
|
||
|
||
```ts
|
||
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:
|
||
|
||
```ts
|
||
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.
|