# Race condition на refresh токенах **Контекст:** Два параллельных `POST /auth/refresh` с одним refresh token вызывают ложное срабатывание детекции кражи. ## Суть Без блокировки потоков: 1. Request A: `GetValid` → found 2. Request B: `GetValid` → found (на том же токене, ещё не удалён) 3. Request A: `Delete` → ok 4. Request A: `Create` → новый токен T2 5. Request B: `GetRevoked` → found (т.к. A уже удалил) 6. Request B: вызывает `RevokeAllForUser` → **все сессии пользователя сброшены** ## Решение ```go deleted, err := s.refreshTokenRepo.DeleteIfExists(ctx, storedToken.TokenHash) if !deleted { // Токен уже был сротирован — значит, это конкурентный запрос, не кража return nil, "", ErrTokenReused } ``` ## Схема ```mermaid sequenceDiagram participant A as Request A participant B as Request B participant DB as PostgreSQL A->>DB: GetValid → found B->>DB: GetValid → found A->>DB: DeleteIfExists → true (deleted) A->>DB: Create (new token) B->>DB: DeleteIfExists → false (already deleted) Note over B: ErrTokenReused (graceful, без RevokeAll) ``` Файлы: `backend/internal/services/auth.go:236-243`, `backend/internal/repository/refresh_tokens.go:143-155` ## Связанные заметки - [[decision-jwt-refresh-storage]] - [[backend-auth-security]] #golang #concurrency #auth #race-condition #security