2026-09-20 23:09:59 +02:00
2026-09-20 23:09:59 +02:00
2026-09-20 23:09:59 +02:00
2026-09-20 23:09:59 +02:00
2026-09-20 23:09:59 +02:00
2026-09-20 23:09:59 +02:00
2026-09-20 23:09:59 +02:00
2026-09-20 23:09:59 +02:00
2026-09-20 23:09:59 +02:00
2026-09-20 23:09:59 +02:00
2026-09-20 23:09:59 +02:00
2026-09-20 23:09:59 +02:00

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

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

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:

cp config.example.toml config.toml

config.toml wird absichtlich nicht versioniert. Die wichtigsten Werte sind:

[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:

cp web/.env.example web/.env.local
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:

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:

devenv up

Alternativ einzeln:

# 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:

cargo fmt --check
cargo check
cargo test
cargo clippy -- -D warnings

Frontend aus web/:

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.
S
Description
No description provided
Readme
73 KiB
Languages
Rust 51.4%
Vue 23.2%
TypeScript 22%
HTML 2%
Nix 1.4%