# πŸ”„ Π‘Ρ…Π΅ΠΌΠ° Ρ€Π°Π±ΠΎΡ‚Ρ‹ OpenID Connect с ZITADEL ## Π’ΠΈΠ·ΡƒΠ°Π»ΡŒΠ½Π°Ρ схСма ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒβ”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ 1. ΠžΡ‚ΠΊΡ€Ρ‹Π²Π°Π΅Ρ‚ страницу Π²Ρ…ΠΎΠ΄Π° β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Frontend (React) β”‚ β”‚ http://localhost:3000 β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Π€ΠΎΡ€ΠΌΠ° Π²Ρ…ΠΎΠ΄Π° β”‚ β”‚ β”‚ β”‚ - Π›ΠΎΠ³ΠΈΠ½/ΠŸΠ°Ρ€ΠΎΠ»ΡŒ β”‚ β”‚ β”‚ β”‚ - Кнопка ZITADEL πŸ” β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ 2. НаТимаСт "Π’ΠΎΠΉΡ‚ΠΈ Ρ‡Π΅Ρ€Π΅Π· ZITADEL" β”‚ GET /api/auth/oidc/zitadel/login β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Backend (FastAPI) β”‚ β”‚ http://localhost:8000 β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ OAuth Client β”‚ β”‚ β”‚ β”‚ - Π‘ΠΎΠ·Π΄Π°Ρ‘Ρ‚ authorize URL β”‚ β”‚ β”‚ β”‚ - ДобавляСт state, nonce β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ 3. Redirect Π½Π° ZITADEL β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ZITADEL β”‚ β”‚ https://your-instance.zitadel.cloudβ”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Π‘Ρ‚Ρ€Π°Π½ΠΈΡ†Π° Π²Ρ…ΠΎΠ΄Π° β”‚ β”‚ β”‚ β”‚ - Email/Username β”‚ β”‚ β”‚ β”‚ - Password β”‚ β”‚ β”‚ β”‚ - 2FA (ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎ) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ 4. ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ Π²Π²ΠΎΠ΄ΠΈΡ‚ Π΄Π°Π½Π½Ρ‹Π΅ β”‚ 5. ZITADEL провСряСт β”‚ 6. Redirect с code β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Backend (FastAPI) β”‚ β”‚ /api/auth/oidc/zitadel/callback β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ 7. ОбмСн code Π½Π° token β”‚ β”‚ β”‚ β”‚ POST /oauth/token β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ 8. ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ userinfo β”‚ β”‚ β”‚ β”‚ - email β”‚ β”‚ β”‚ β”‚ - name β”‚ β”‚ β”‚ β”‚ - sub (user ID) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ 9. Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅/ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ β”‚ β”‚ β”‚ β”‚ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π² users.json β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ 10. ГСнСрация JWT Ρ‚ΠΎΠΊΠ΅Π½Π° β”‚ β”‚ β”‚ β”‚ для MC Panel β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ 11. Redirect Π½Π° frontend β”‚ ?token=xxx&username=yyy β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Frontend (React) β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ 12. ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° callback β”‚ β”‚ β”‚ β”‚ - Π˜Π·Π²Π»Π΅Ρ‡Π΅Π½ΠΈΠ΅ token β”‚ β”‚ β”‚ β”‚ - Π‘ΠΎΡ…Ρ€Π°Π½Π΅Π½ΠΈΠ΅ Π² localStorageβ”‚ β”‚ β”‚ β”‚ - ΠžΡ‡ΠΈΡΡ‚ΠΊΠ° URL β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ 13. АвтоматичСский Π²Ρ…ΠΎΠ΄ β”‚ β”‚ β”‚ β”‚ - Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π΄Π°Π½Π½Ρ‹Ρ… β”‚ β”‚ β”‚ β”‚ - Показ ΠΏΠ°Π½Π΅Π»ΠΈ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ 14. ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ Π² систСмС! β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ MC Panel Dashboard β”‚ β”‚ - Π‘Π΅Ρ€Π²Π΅Ρ€Ρ‹ β”‚ β”‚ - Π’ΠΈΠΊΠ΅Ρ‚Ρ‹ β”‚ β”‚ - Π›ΠΈΡ‡Π½Ρ‹ΠΉ ΠΊΠ°Π±ΠΈΠ½Π΅Ρ‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ## Π”Π΅Ρ‚Π°Π»ΡŒΠ½Ρ‹ΠΉ ΠΏΠΎΡ‚ΠΎΠΊ Π΄Π°Π½Π½Ρ‹Ρ… ### Π¨Π°Π³ 1-2: Π˜Π½ΠΈΡ†ΠΈΠ°Ρ†ΠΈΡ Π²Ρ…ΠΎΠ΄Π° ``` ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ β†’ Frontend ↓ Frontend β†’ Backend: GET /api/auth/oidc/zitadel/login ↓ Backend создаёт OAuth URL: - client_id - redirect_uri - scope: openid email profile - state (CSRF Π·Π°Ρ‰ΠΈΡ‚Π°) - nonce (replay Π·Π°Ρ‰ΠΈΡ‚Π°) ``` ### Π¨Π°Π³ 3-6: АутСнтификация Π² ZITADEL ``` Backend β†’ ZITADEL: Redirect Π½Π° /oauth/authorize ↓ ZITADEL ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ Ρ„ΠΎΡ€ΠΌΡƒ Π²Ρ…ΠΎΠ΄Π° ↓ ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ Π²Π²ΠΎΠ΄ΠΈΡ‚ Π΄Π°Π½Π½Ρ‹Π΅ ↓ ZITADEL провСряСт ΡƒΡ‡Ρ‘Ρ‚Π½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅ ↓ ZITADEL β†’ Backend: Redirect с code URL: /callback?code=xxx&state=yyy ``` ### Π¨Π°Π³ 7-8: ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ Π΄Π°Π½Π½Ρ‹Ρ… ``` Backend β†’ ZITADEL: POST /oauth/token ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹: - code - client_id - client_secret - redirect_uri ↓ ZITADEL β†’ Backend: access_token + id_token ↓ Backend ΠΈΠ·Π²Π»Π΅ΠΊΠ°Π΅Ρ‚ userinfo: { "sub": "123456789012345678", "email": "user@example.com", "name": "John Doe", "picture": "https://..." } ``` ### Π¨Π°Π³ 9-10: Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ ``` Backend провСряСт users.json: - Π˜Ρ‰Π΅Ρ‚ ΠΏΠΎ oidc_id = "zitadel:123456789012345678" Если Π½Π°ΠΉΠ΄Π΅Π½: - ΠžΠ±Π½ΠΎΠ²Π»ΡΠ΅Ρ‚ email, name, picture Если Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½: - Π‘ΠΎΠ·Π΄Π°Ρ‘Ρ‚ Π½ΠΎΠ²ΠΎΠ³ΠΎ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ - Π“Π΅Π½Π΅Ρ€ΠΈΡ€ΡƒΠ΅Ρ‚ username ΠΈΠ· email - Роль: "user" - ΠŸΡƒΡΡ‚ΠΎΠΉ ΠΏΠ°Ρ€ΠΎΠ»ΡŒ (OIDC ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ) Backend создаёт JWT Ρ‚ΠΎΠΊΠ΅Π½: { "sub": "john_doe", "role": "user", "exp": 1234567890 } ``` ### Π¨Π°Π³ 11-14: Π’ΠΎΠ·Π²Ρ€Π°Ρ‚ Π² ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ``` Backend β†’ Frontend: Redirect URL: http://localhost:3000/?token=xxx&username=yyy ↓ Frontend (useEffect): - Π˜Π·Π²Π»Π΅ΠΊΠ°Π΅Ρ‚ token ΠΈ username ΠΈΠ· URL - БохраняСт Π² localStorage - ΠžΡ‡ΠΈΡ‰Π°Π΅Ρ‚ URL (history.replaceState) - ΠžΠ±Π½ΠΎΠ²Π»ΡΠ΅Ρ‚ состояниС (setToken, setUser) ↓ Frontend Π·Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ Π΄Π°Π½Π½Ρ‹Π΅: - GET /api/auth/me (ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Ρ‚ΠΎΠΊΠ΅Π½Π°) - GET /api/servers (список сСрвСров) ↓ ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ Π²ΠΈΠ΄ΠΈΡ‚ панСль управлСния ``` ## Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° Π΄Π°Π½Π½Ρ‹Ρ… ### ZITADEL userinfo ```json { "sub": "123456789012345678", "email": "user@example.com", "email_verified": true, "name": "John Doe", "given_name": "John", "family_name": "Doe", "picture": "https://avatar.url", "locale": "en" } ``` ### MC Panel user (users.json) ```json { "john_doe": { "username": "john_doe", "password": "", "role": "user", "servers": [], "oidc_id": "zitadel:123456789012345678", "email": "user@example.com", "name": "John Doe", "picture": "https://avatar.url", "provider": "zitadel", "created_at": "2026-01-15T12:00:00" } } ``` ### JWT Ρ‚ΠΎΠΊΠ΅Π½ (MC Panel) ```json { "sub": "john_doe", "role": "user", "exp": 1737820800, "iat": 1737216000 } ``` ## Π‘Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΡΡ‚ΡŒ ### Π—Π°Ρ‰ΠΈΡ‚Π° ΠΎΡ‚ Π°Ρ‚Π°ΠΊ 1. **CSRF (Cross-Site Request Forgery)** - State parameter провСряСтся - Π‘Π»ΡƒΡ‡Π°ΠΉΠ½ΠΎΠ΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ запроса 2. **Replay Π°Ρ‚Π°ΠΊΠΈ** - Nonce Π² id_token - ΠžΠ΄Π½ΠΎΡ€Π°Π·ΠΎΠ²ΠΎΠ΅ использованиС code 3. **Man-in-the-Middle** - HTTPS обязатСлСн Π² ΠΏΡ€ΠΎΠ΄Π°ΠΊΡˆΠ΅Π½Π΅ - ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° redirect_uri 4. **Token theft** - JWT с истСчСниСм (7 Π΄Π½Π΅ΠΉ) - Π₯Ρ€Π°Π½Π΅Π½ΠΈΠ΅ Π² localStorage (XSS Π·Π°Ρ‰ΠΈΡ‚Π° Π½ΡƒΠΆΠ½Π°) ### Π Π΅ΠΊΠΎΠΌΠ΅Π½Π΄Π°Ρ†ΠΈΠΈ для ΠΏΡ€ΠΎΠ΄Π°ΠΊΡˆΠ΅Π½Π° ``` βœ“ Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ HTTPS βœ“ ΠΠ°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ CORS ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎ βœ“ Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ rate limiting βœ“ Π›ΠΎΠ³ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ всС Π²Ρ…ΠΎΠ΄Ρ‹ βœ“ ΠœΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΡ‚ΡŒ ΠΏΠΎΠ΄ΠΎΠ·Ρ€ΠΈΡ‚Π΅Π»ΡŒΠ½ΡƒΡŽ Π°ΠΊΡ‚ΠΈΠ²Π½ΠΎΡΡ‚ΡŒ βœ“ РСгулярно ΠΎΠ±Π½ΠΎΠ²Π»ΡΡ‚ΡŒ client_secret βœ“ Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ refresh tokens ``` ## Π“ΠΎΡ‚ΠΎΠ²ΠΎ! πŸŽ‰ Π‘Ρ…Π΅ΠΌΠ° ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ ΠΏΠΎΠ»Π½Ρ‹ΠΉ Ρ†ΠΈΠΊΠ» Π°ΡƒΡ‚Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΠΈ Ρ‡Π΅Ρ€Π΅Π· ZITADEL. **Всё Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ автоматичСски ΠΈ бСзопасно!** πŸ”