Skip to content

Аутентификация

Руководство по работе с аутентификацией в Integ API.

Обзор

Integ API использует JWT (JSON Web Tokens) для аутентификации. После успешного входа вы получаете токен, который нужно отправлять в заголовке Authorization для доступа к защищенным эндпоинтам.

Flow аутентификации

mermaid
sequenceDiagram
    participant Client
    participant API
    participant DB

    Client->>API: POST /auth/sign-in<br/>{email, password}
    API->>DB: Проверка credentials
    DB-->>API: User data
    API->>API: Генерация JWT
    API-->>Client: {accessToken, user}

    Client->>API: GET /integrations<br/>Authorization: Bearer token
    API->>API: Валидация токена
    API->>DB: Получение данных
    DB-->>API: Data
    API-->>Client: Response

Регистрация

POST /api/auth/sign-up

Создание нового пользователя.

Request:

http
POST /api/auth/sign-up
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "securePassword123"
}

Response (201 Created):

json
{
	"id": "uuid",
	"email": "user@example.com",
	"verificationStatus": "pending",
	"createdAt": "2025-01-01T00:00:00.000Z"
}

Validation Rules:

  • email: валидный email адрес, уникальный
  • password: минимум 8 символов

Errors:

json
// 400 Bad Request - Email уже существует
{
  "statusCode": 400,
  "message": "User with this email already exists",
  "error": "Bad Request"
}

// 400 Bad Request - Невалидные данные
{
  "statusCode": 400,
  "message": [
    "email must be an email",
    "password must be longer than or equal to 8 characters"
  ],
  "error": "Bad Request"
}

Вход

POST /api/auth/sign-in

Аутентификация пользователя и получение JWT токена.

Request:

http
POST /api/auth/sign-in
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "securePassword123"
}

Response (200 OK):

json
{
	"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
	"user": {
		"id": "uuid",
		"email": "user@example.com",
		"verificationStatus": "verified"
	}
}

Errors:

json
// 401 Unauthorized - Неверные credentials
{
  "statusCode": 401,
  "message": "Invalid credentials",
  "error": "Unauthorized"
}

// 400 Bad Request - Невалидные данные
{
  "statusCode": 400,
  "message": [
    "email must be an email",
    "password should not be empty"
  ],
  "error": "Bad Request"
}

Проверка токена

GET /api/auth/me

Получение информации о текущем пользователе.

Request:

http
GET /api/auth/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Response (200 OK):

json
{
	"id": "uuid",
	"email": "user@example.com",
	"verificationStatus": "verified",
	"createdAt": "2025-01-01T00:00:00.000Z"
}

Errors:

json
// 401 Unauthorized - Токен отсутствует или невалидный
{
	"statusCode": 401,
	"message": "Unauthorized",
	"error": "Unauthorized"
}

Обновление профиля

PATCH /api/auth/me

Обновление профиля текущего пользователя.

Request:

http
PATCH /api/auth/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

{
  "email": "new-email@example.com"
}

Response (200 OK):

json
{
	"id": "uuid",
	"email": "new-email@example.com",
	"verificationStatus": "verified",
	"createdAt": "2025-01-01T00:00:00.000Z"
}

Использование JWT токена

В заголовке Authorization

http
Authorization: Bearer <your-jwt-token>

Примеры

cURL

bash
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

curl -X GET http://localhost:3000/api/integrations \
  -H "Authorization: Bearer $TOKEN"

JavaScript

javascript
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";

const response = await fetch("http://localhost:3000/api/integrations", {
	headers: {
		Authorization: `Bearer ${token}`
	}
});

Axios

typescript
import axios from "axios";

const api = axios.create({
	baseURL: "http://localhost:3000/api"
});

// Установка токена для всех запросов
api.defaults.headers.common["Authorization"] = `Bearer ${token}`;

// Или для конкретного запроса
const response = await api.get("/integrations", {
	headers: {
		Authorization: `Bearer ${token}`
	}
});

JWT Token структура

Payload

Токен содержит следующую информацию:

json
{
	"sub": "user-uuid",
	"email": "user@example.com",
	"iat": 1704067200,
	"exp": 1704153600
}
  • sub (subject) - ID пользователя
  • email - Email пользователя
  • iat (issued at) - Время создания токена
  • exp (expiration) - Время истечения токена

Срок действия

По умолчанию токен действителен 24 часа.

Refresh Tokens (Будущее)

В будущих версиях будет добавлена поддержка refresh tokens для обновления access token без повторного входа.

Безопасность

Best Practices

DO:

  • Храните токен безопасно (localStorage, sessionStorage, cookies с httpOnly)
  • Отправляйте токен только по HTTPS в production
  • Обрабатывайте ошибки 401 (перенаправление на login)
  • Очищайте токен при logout

DON'T:

  • Не храните токен в URL или query параметрах
  • Не храните пароль на клиенте
  • Не логируйте токен в консоль
  • Не отправляйте токен через незащищенное соединение

Валидация токена

API автоматически проверяет:

  • Подпись токена (signature)
  • Срок действия (expiration)
  • Формат payload

При невалидном токене возвращается 401 Unauthorized.

Защищенные эндпоинты

Все эндпоинты, кроме публичных, требуют JWT токен:

Публичные (без токена)

  • POST /api/auth/sign-up
  • POST /api/auth/sign-in
  • GET /health

Защищенные (требуют токен)

  • GET /api/auth/me
  • PATCH /api/auth/me
  • GET /api/integrations
  • POST /api/integrations
  • GET /api/core/secrets
  • И все остальные...

Примеры полного flow

React + TypeScript

typescript
import { useState } from 'react';
import axios from 'axios';

const api = axios.create({
  baseURL: 'http://localhost:3000/api'
});

function AuthExample() {
  const [token, setToken] = useState<string | null>(null);

  const login = async (email: string, password: string) => {
    try {
      const { data } = await api.post('/auth/sign-in', { email, password });
      setToken(data.accessToken);

      // Установка токена для будущих запросов
      api.defaults.headers.common['Authorization'] = `Bearer ${data.accessToken}`;

      // Сохранение в localStorage
      localStorage.setItem('token', data.accessToken);

      return data;
    } catch (error) {
      console.error('Login failed:', error);
      throw error;
    }
  };

  const fetchIntegrations = async () => {
    try {
      const { data } = await api.get('/integrations');
      return data;
    } catch (error) {
      if (error.response?.status === 401) {
        // Токен истек или невалидный - перенаправить на login
        setToken(null);
        localStorage.removeItem('token');
      }
      throw error;
    }
  };

  const logout = () => {
    setToken(null);
    localStorage.removeItem('token');
    delete api.defaults.headers.common['Authorization'];
  };

  // Восстановление токена при загрузке
  useEffect(() => {
    const savedToken = localStorage.getItem('token');
    if (savedToken) {
      setToken(savedToken);
      api.defaults.headers.common['Authorization'] = `Bearer ${savedToken}`;
    }
  }, []);

  return (
    // ... UI components
  );
}

Node.js Backend-to-Backend

typescript
import axios from "axios";

class IntegApiClient {
	private token: string | null = null;
	private api = axios.create({
		baseURL: process.env.INTEG_API_URL
	});

	async authenticate() {
		const { data } = await this.api.post("/auth/sign-in", {
			email: process.env.INTEG_API_EMAIL,
			password: process.env.INTEG_API_PASSWORD
		});

		this.token = data.accessToken;
		this.api.defaults.headers.common["Authorization"] = `Bearer ${this.token}`;
	}

	async getIntegrations() {
		if (!this.token) {
			await this.authenticate();
		}

		try {
			const { data } = await this.api.get("/integrations");
			return data;
		} catch (error) {
			if (error.response?.status === 401) {
				// Повторная аутентификация
				await this.authenticate();
				const { data } = await this.api.get("/integrations");
				return data;
			}
			throw error;
		}
	}
}

Troubleshooting

401 Unauthorized

Причины:

  • Токен отсутствует
  • Токен невалидный (поврежден, неправильная подпись)
  • Токен истек
  • Неправильный формат заголовка

Решение:

  • Проверьте формат: Authorization: Bearer <token>
  • Проверьте срок действия токена
  • Повторите login для получения нового токена

CORS ошибки

Причины:

  • Фронтенд на другом домене/порту
  • Отсутствует заголовок Origin

Решение:

  • Убедитесь, что CLIENT_BASE_URL в environment настроен правильно
  • В development CORS открыт для всех (*)

Следующие шаги