OAuth 2.0
Зачем нужно
OAuth 2.0 — протокол авторизации, позволяющий приложению получить доступ к ресурсам пользователя на другом сервисе без передачи пароля. Когда вы нажимаете «Войти через Google» — это OAuth. Приложение получает ограниченный токен доступа, а пароль остаётся у Google.
Где используется
- Вход через соцсети (Google, GitHub, VK, Telegram)
- Доступ к API от имени пользователя (GitHub API, Google Drive)
- Микросервисы — авторизация между сервисами
- Мобильные приложения
Роли в OAuth
Resource Owner — пользователь (владелец данных)
Client — приложение, которое хочет доступ
Authorization Server — сервер авторизации (Google, GitHub)
Resource Server — API с защищёнными ресурсами
Authorization Code Flow (основной)
Самый безопасный flow для серверных приложений:
1. Пользователь нажимает "Войти через GitHub"
↓
2. Приложение → редирект на GitHub:
https://github.com/login/oauth/authorize?
client_id=abc123&
redirect_uri=http://myapp.com/callback&
scope=read:user&
state=random123
↓
3. Пользователь логинится на GitHub, даёт согласие
↓
4. GitHub → редирект обратно с code:
http://myapp.com/callback?code=xyz789&state=random123
↓
5. Сервер приложения → обменивает code на token:
POST https://github.com/login/oauth/access_token
{ client_id, client_secret, code, redirect_uri }
↓
6. GitHub → возвращает access_token
↓
7. Приложение → запрашивает данные пользователя:
GET https://api.github.com/user
Authorization: Bearer access_token
Реализация (Express + GitHub)
const express = require('express');
const app = express;
const CLIENT_ID = process.env.GITHUB_CLIENT_ID;
const CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET;
const REDIRECT_URI = 'http://localhost:3000/auth/github/callback';
// Шаг 1: Редирект на GitHub
app.get('/auth/github', (req, res) => {
const state = Math.random.toString(36).substring(7);
// Сохранить state в session для проверки (защита от CSRF)
const url = new URL('https://github.com/login/oauth/authorize');
url.searchParams.set('client_id', CLIENT_ID);
url.searchParams.set('redirect_uri', REDIRECT_URI);
url.searchParams.set('scope', 'read:user user:email');
url.searchParams.set('state', state);
res.redirect(url.toString());
});
// Шаг 2: Callback — обмен code → token
app.get('/auth/github/callback', async (req, res) => {
const { code, state } = req.query;
// Проверить state (защита от CSRF)
// Обмен code на access_token
const tokenResponse = await fetch(
'https://github.com/login/oauth/access_token',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
code,
redirect_uri: REDIRECT_URI,
}),
}
);
const { access_token } = await tokenResponse.json();
// Шаг 3: Получить данные пользователя
const userResponse = await fetch('https://api.github.com/user', {
headers: { Authorization: `Bearer ${access_token}` },
});
const githubUser = await userResponse.json();
// Создать/найти пользователя в своей БД
// Выдать свой JWT или создать сессию
// ...
res.redirect('/dashboard');
});
PKCE (для SPA и мобильных приложений)
Proof Key for Code Exchange — расширение для клиентов без server secret:
// Шаг 0: Генерация code_verifier и code_challenge
function generatePKCE() {
// Случайная строка 43-128 символов
const verifier = crypto.randomUUID + crypto.randomUUID;
// SHA-256 хеш verifier, закодированный в Base64URL
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
return crypto.subtle.digest('SHA-256', data).then(hash => {
const challenge = btoa(String.fromCharCode(...new Uint8Array(hash)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
return { verifier, challenge };
});
}
// Шаг 1: Редирект с code_challenge
const { verifier, challenge } = await generatePKCE;
sessionStorage.setItem('pkce_verifier', verifier);
const authUrl = new URL('https://auth.example.com/authorize');
authUrl.searchParams.set('client_id', CLIENT_ID);
authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', 'openid profile');
authUrl.searchParams.set('code_challenge', challenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('state', 'random123');
window.location.href = authUrl.toString();
// Шаг 2: Callback — обмен code + verifier на token
const code = new URLSearchParams(window.location.search).get('code');
const verifier = sessionStorage.getItem('pkce_verifier');
const tokenResponse = await fetch('https://auth.example.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: CLIENT_ID,
code,
redirect_uri: REDIRECT_URI,
code_verifier: verifier, // Сервер проверяет SHA256(verifier) = challenge
}),
});
const { access_token, refresh_token } = await tokenResponse.json();
Другие Flow
Implicit Flow (устаревший)
Токен возвращается сразу в URL (без обмена code):
redirect_uri#access_token=abc&token_type=bearer
❌ Не рекомендуется — токен виден в URL, истории браузера, referer
✅ Заменён на Authorization Code + PKCE
Client Credentials Flow
Для server-to-server (без пользователя):
POST /oauth/token
{
grant_type: "client_credentials",
client_id: "app_id",
client_secret: "app_secret",
scope: "read write"
}
→ { access_token: "..." }
Используется: микросервисы, cron-задачи, бэкенд-интеграции
Scopes (области доступа)
// GitHub scopes
read:user — читать профиль
user:email — читать email
repo — доступ к репозиториям
gist — доступ к gist
// Google scopes
openid — OpenID Connect
profile — имя, фото
email — email
https://www.googleapis.com/auth/drive.readonly — Google Drive
Частые ошибки
- Нет проверки
state— CSRF-атака: злоумышленник подставляет свой code - Implicit Flow вместо PKCE — токен в URL небезопасен
- Client secret на клиенте — secret нельзя хранить в SPA/мобильном приложении
- Широкие scopes — запрашивают
repoкогда нужен толькоread:user - Нет обработки ошибок — redirect может вернуть
error=access_denied - Хранят access_token бессрочно — нужен refresh flow
Практика
- Зарегистрировать OAuth App на GitHub
- Реализовать Authorization Code Flow на Express
- Получить профиль пользователя через GitHub API
- Добавить PKCE flow для SPA
- Реализовать «Войти через Google» с OpenID Connect
Связанные темы
- JWT — JWT как access token в OAuth
- Cookies и сессии — сессия после OAuth-логина
- CORS — кросс-доменные запросы к OAuth-провайдерам
- HTTP протокол — Authorization header с Bearer token