Оглавление
Первые пару раз я просто копировал конфиги из примеров и надеялся, что «оно заработает». 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
— дай знать. Я могу подготовить практическую статью с примерами и архитектурой.
Комментарии: