Владислав Калачев

OAuth 2.0 и OpenID Connect в Frontend: как это работает?

13

Первые пару раз я просто копировал конфиги из примеров и надеялся, что «оно заработает». OAuth выглядел как магия: что-то редиректит, что-то возвращает код, потом из него рождаются токены.

Но чем больше я работал с авторизацией, тем яснее всё становилось. В этой статье расскажу, как OAuth 2.0 и OpenID Connect устроены под капотом, и как с ними работать во фронтенде.

Что такое OAuth 2.0?

OAuth 2.0 — это протокол авторизации. Он не отвечает на вопрос «Кто ты?», он разрешает или запрещает доступ.

Допустим, ты пишешь приложение, которое должно получить доступ к Google Calendar пользователя. Ты отправляешь пользователя на страницу Google, он соглашается, и тебе возвращают токен. С этим токеном ты можешь читать и писать в его календарь.

Важно: OAuth говорит «что можно сделать», но не говорит «кто это делает».

Что такое OpenID Connect?

OpenID Connect (или OIDC) — это надстройка над OAuth 2.0, которая добавляет идентификацию. То есть, когда пользователь залогинился, ты не просто получаешь разрешение, ты ещё получаешь подтверждение, кто он такой: его ID, email, имя, аватар и прочее.

Это особенно важно для фронтенда, когда тебе нужно отобразить профиль пользователя, дать ему доступ к личному кабинету и т.д.

Как это работает: шаг за шагом

Разберёмся по порядку, как это выглядит в реальном SPA-приложении:

1. Пользователь нажимает “Войти”

Ты отправляешь его на авторизацию:

window.location.href = `https://auth-server.com/authorize
  ?client_id=frontend-app
  &redirect_uri=https://my-app.com/callback
  &response_type=code
  &scope=openid profile email`;

🔍 Обрати внимание: scope=openid — это сигнал серверу, что ты хочешь использовать OIDC.

2. Пользователь входит и соглашается

Если всё ок, сервер авторизации перенаправляет пользователя обратно в твоё приложение, на redirect_uri, с параметром code в URL:

https://my-app.com/callback?code=xyz123

3. Получение токенов (через backend)

Теперь тебе нужно обменять authorization code на настоящие токены: access_token, id_token, и, возможно, refresh_token.

Но важно: этот шаг выполняется на сервере, а не в браузере.

Почему?

Потому что для обмена тебе нужен client_secret, а его нельзя хранить на фронте.

Какие токены бывают?

access_token — пропуск к защищённым API. Например, к GET /user/profile.
id_token — JWT, в котором содержатся данные о пользователе. Используется для идентификации.
refresh_token — обновляет access_token, когда тот устарел. Обычно хранится на бэкенде.
Пример id_token (JWT):

{
  "sub": "1234567890",
  "name": "John Doe",
  "email": "john@example.com",
  "exp": 1710000000
}

Где хранить токены?

Это один из главных вопросов в фронтенде.

Плохая идея: localStorage

Хранить токены в localStorage — просто и удобно. Но это уязвимо для XSS-атак.

Лучше: память + HttpOnly cookies

Если токен нужен только во время сессии — держи его в памяти (например, в React Context).

Если токен нужен при каждом запросе к API — пусть сервер установит HttpOnly cookie. Такой cookie нельзя прочитать из JS, он автоматически отправляется с каждым запросом.

Что такое PKCE?

PKCE (Proof Key for Code Exchange) — это защита для публичных клиентов (SPA, мобильных приложений), у которых нет client_secret.

Суть: ты генерируешь случайный code_verifier, создаёшь из него code_challenge (через SHA256), и отправляешь его при авторизации. Когда ты потом обмениваешь code на токены, ты передаёшь code_verifier, и сервер сверяет его с code_challenge.

👉 PKCE защищает от атак с подменой кода авторизации.

Хорошая новость — библиотеки вроде oidc-client-ts делают всё это автоматически.

Типичная архитектура фронта с OAuth 2.0 + OIDC

[User] → [Frontend SPA] → (Redirect to Auth Server)
                              ↓
                      [Login / Consent]
                              ↓
      ← (Redirect back with code) ← [Auth Server]
            ↓
      [Frontend → Backend] → Exchange code for tokens
            ↓
       [Backend stores tokens / sets cookies]
            ↓
       [Frontend receives session info]

Используем oidc-client-ts в React

Вот базовая интеграция oidc-client-ts:

import { AuthProvider, useAuth } from 'react-oidc-context';

const config = {
  authority: 'https://auth-server.com',
  client_id: 'frontend-app',
  redirect_uri: window.location.origin + '/callback',
  scope: 'openid profile email',
  response_type: 'code',
};

export const AppWrapper = () => (
   <AuthProvider {...config}>
     <App />
   </AuthProvider>
);

function App() {
  const auth = useAuth();

  if (auth.isLoading) return  <p>Загрузка... </p>;

  if (!auth.isAuthenticated) {
    return  <button onClick={() => auth.signinRedirect()}>Войти</button>;
  }

  return (
     <div>
       <p>Привет, {auth.user?.profile.name} </p>
       <button onClick={() => auth.signoutRedirect()}>Выйти</button>
     </div>
  );
}

Подводные камни

🔁 Преждевременные вызовы API

Пока авторизация не завершена, access_token ещё нет. Нужно дождаться, пока библиотека его получит.
🧠 Хранение состояния

Если ты используешь React Router, убедись, что после возврата с redirect_uri ты правильно сохраняешь путь, откуда пришёл пользователь.
🐞 Необработанные ошибки

У пользователя может быть отключен 3rd-party cookies, может истечь срок access_token, может не совпасть время на клиенте. Всё это надо обрабатывать.

Когда OIDC не нужен?

Если ты просто хочешь «войти через Google» и получить email/имя — OIDC идеален.

Но если тебе нужен доступ к чужим данным (например, Google Drive, Spotify API) — OAuth 2.0 без OIDC может быть достаточно.

Заключение

OAuth 2.0 и OpenID Connect — это не про авторизацию «для галочки». Это инфраструктура безопасности, и она работает как часы, если правильно её использовать.

Что нужно запомнить:
OAuth = доступ, OIDC = доступ + кто ты
— Во фронте работает редирект + обработка code, а токены получаются на бэкенде
— Используй PKCE и не храни токены в localStorage
— Лучше довериться библиотеке, чем писать всё вручную


Если ты хочешь подробный гайд для Next.js, или сравнение oidc-client-ts с next-auth — дай знать. Я могу подготовить практическую статью с примерами и архитектурой.

Комментарии:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *