# Rust GraphQL Zitadel Kleines Lernprojekt für eine geschützte GraphQL-API mit Rust, Actix-Web, async-graphql und Zitadel sowie einer Vue/Vite-SPA als Frontend. ## Architektur ```text Vue SPA (Vite, :5173) | | OIDC Authorization Code + PKCE v Zitadel | | Access Token (JWT) v Rust API (Actix-Web, :8080) | | JWT-Signatur, Issuer, Audience und Ablaufzeit prüfen v async-graphql ``` Das Frontend führt den Login bei Zitadel durch. Die Rust-Anwendung führt keinen Login durch, sondern arbeitet als OAuth-Resource-Server: Jede GraphQL-Anfrage muss ein gültiges Access Token im `Authorization`-Header enthalten. ## Verzeichnisstruktur ```text src/ ├── main.rs # Konfiguration laden und Actix-Server starten ├── config.rs # config.toml in typisierte Settings laden ├── auth.rs # OIDC Discovery, JWKS und JWT-Validierung └── graphql.rs # GraphQL-Schema und geschützter Handler web/src/ ├── App.vue # Seitenfluss und Zusammenspiel der Module ├── auth/oidc.ts # OIDC-Client und Zitadel-Redirects ├── auth/useAuth.ts # Authentifizierungszustand im Vue-Lifecycle ├── graphql/client.ts # GraphQL-Request mit Bearer Token └── components/ # Login- und Ergebnisdarstellung ``` ## Konfiguration Backend-Konfiguration: ```bash cp config.example.toml config.toml ``` `config.toml` wird absichtlich nicht versioniert. Die wichtigsten Werte sind: ```toml [server] host = "127.0.0.1" port = 8080 [zitadel] issuer_url = "https://deine-instanz.zitadel.cloud" audience = "deine-api-audience" ``` `issuer_url` ist die Zitadel-Instanz. `audience` muss einem Wert im `aud`- Claim des Access Tokens entsprechen, für dieses Projekt normalerweise der Zitadel-Project-ID. Frontend-Konfiguration: ```bash cp web/.env.example web/.env.local ``` ```env VITE_ZITADEL_AUTHORITY=https://deine-instanz.zitadel.cloud VITE_ZITADEL_CLIENT_ID=deine-spa-client-id VITE_ZITADEL_PROJECT_ID=deine-projekt-id ``` Die SPA muss in Zitadel als Single-Page Application angelegt sein. Für die lokale Entwicklung werden diese Redirect-URIs benötigt: ```text http://localhost:5173/callback http://localhost:5173/ ``` Die erste URI ist die Login-Callback-URI, die zweite die Post-Logout-URI. Wenn die Anwendung über `127.0.0.1` geöffnet wird, müssen die entsprechenden URIs mit `127.0.0.1` ebenfalls in Zitadel eingetragen werden. Der Access-Token-Typ der Zitadel-Anwendung muss auf **JWT** stehen. Ein opaker beziehungsweise verschlüsselter Token kann nicht mit den öffentlichen JWKS-Schlüsseln lokal validiert werden; dafür wäre stattdessen Token Introspection erforderlich. ## Lokale Entwicklung Mit Devenv werden Backend und Frontend gemeinsam gestartet: ```bash devenv up ``` Alternativ einzeln: ```bash # Repository-Root cargo run # web/ ``` Der Vite-Entwicklungsserver proxied `/graphql` an das Rust-Backend. Dadurch ruft der Browser `/graphql` auf Port `5173` auf, während die Anfrage intern an `http://127.0.0.1:8080/graphql` weitergeleitet wird. ## Request-Fluss 1. Die SPA startet den Authorization-Code-Flow mit PKCE. 2. Zitadel authentifiziert den Benutzer und leitet zu `/callback` zurück. 3. `oidc-client-ts` tauscht den Code gegen Tokens und speichert die Session in `sessionStorage`. 4. Das Frontend sendet das Access Token als `Bearer`-Token an `/graphql`. 5. Actix-Web validiert Signatur, Issuer, Audience und Ablaufzeit. 6. Erst danach wird die GraphQL-Abfrage ausgeführt. ## Prüfungen Backend aus dem Repository-Root: ```bash cargo fmt --check cargo check cargo test cargo clippy -- -D warnings ``` Frontend aus `web/`: ```bash pnpm build ``` ## Häufige Fehler - `redirect_uri is missing`: Die exakte Callback-URI fehlt in der Zitadel-SPA-Anwendung. - `401 Unauthorized`: Das Access Token fehlt, ist abgelaufen oder Issuer/Audience stimmen nicht. - `Base64 error` mit fünf Token-Teilen: Zitadel liefert keinen normalen signierten JWT; den Access-Token-Typ auf JWT stellen oder Introspection implementieren.