Реализация OAuth 2.0 в Express с GitHub

Что такое OAuth 2.0
OAuth 2.0 — это открытый стандарт для авторизации, который позволяет третьим приложениям получать доступ к ресурсам пользователя на сторонних сервисах без передачи пароля. Кратко: приложение получает токен, который даёт ограниченные права на доступ к данным пользователя.
Ключевой термин: access token — коротко живущий секрет, дающий доступ к данным пользователя от имени приложения.
Обзор потока OAuth
Типичный поток выглядит так:
- Пользователь выбирает вход через провайдера (например, GitHub).
- Ваше приложение перенаправляет пользователя на страницу согласия провайдера с набором параметров (client_id, scope, state и т.д.).
- После согласия провайдер возвращает вашему приложению код авторизации (authorization code).
- Сервер вашего приложения обменивает код на access token, отправив POST-запрос к токен-эндпоинту провайдера.
- С access token вы можете запрашивать данные пользователя с API провайдера.
Важно: всегда используйте параметр state для защиты от CSRF и храните секреты в окружении (ENV).
Быстрая диаграмма потока
flowchart LR
A[Пользователь] --> B[Кнопка 'Войти через GitHub']
B --> C[Перенаправление на GitHub /authorize]
C --> D[Страница согласия GitHub]
D --> E[Redirect с кодом на /github-callback]
E --> F[Сервер: POST /login/oauth/access_token]
F --> G[Получен access token]
G --> H[Запрос /user с токеном]
H --> I[Профиль пользователя отображён в приложении]Подготовка окружения (шаг 1)
- Создайте пустую директорию проекта и перейдите в неё:
mkdir github-app
cd github-app- Инициализируйте npm и включите ES модули:
npm init -y
# Откройте package.json и добавьте:
# "type": "module"- Установите зависимости (Express, Axios, dotenv):
npm install express axios dotenvСоздание простого сервера Express (шаг 2)
Создайте файл index.js в корне проекта и добавьте базовый сервер:
// index.js
import express from "express";
import axios from "axios";
import * as dotenv from "dotenv";
import crypto from "crypto";
dotenv.config();
const app = express();
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`App is running on port ${port}`);
});Пояснение: мы используем ES-модули, dotenv для переменных окружения и подключаем crypto для генерации state.
Роуты для OAuth (шаг 3)
Ниже — два ключевых обработчика: один для перенаправления на GitHub, второй для обработки callback и получения токена.
Обратите внимание на использование параметра state — он нужен для предотвращения CSRF. В простом примере state генерируется и хранится в cookie.
// index.js (добавьте к предыдущему коду)
import cookieParser from "cookie-parser";
app.use(cookieParser());
app.get("/auth", (req, res) => {
const state = crypto.randomBytes(16).toString("hex");
res.cookie("oauth_state", state, { httpOnly: true, sameSite: "lax" });
const params = {
scope: "read:user",
client_id: process.env.CLIENT_ID,
state,
};
const urlEncodedParams = new URLSearchParams(params).toString();
res.redirect(`https://github.com/login/oauth/authorize?${urlEncodedParams}`);
});
app.get("/github-callback", async (req, res) => {
try {
const { code, state } = req.query;
const savedState = req.cookies["oauth_state"];
if (!code || !state || state !== savedState) {
return res.status(400).send("Invalid state or missing code");
}
const body = {
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
code,
state,
};
const options = { headers: { accept: "application/json" } };
const tokenResponse = await axios.post(
"https://github.com/login/oauth/access_token",
body,
options
);
const accessToken = tokenResponse.data.access_token;
if (!accessToken) {
return res.status(500).json({ err: "No access token received" });
}
// Пример запроса профиля пользователя
const userResponse = await axios.get("https://api.github.com/user", {
headers: {
Authorization: `token ${accessToken}`,
Accept: "application/vnd.github.v3+json",
},
});
// В реальном приложении: создать/обновить локального пользователя и сессию
res.json({ profile: userResponse.data });
} catch (err) {
res.status(500).json({ err: err.message });
}
});Важно: в продакшне не передавайте токены в URL. Сохраняйте их в безопасном хранилище (шифрованные куки, сессии на сервере или секретное хранилище).
Пример .env
CLIENT_ID=ваш_client_id_от_github
CLIENT_SECRET=ваш_client_secret_от_github
PORT=3000Создайте файл .env в корне и добавьте в него значения, полученные после регистрации OAuth-приложения в GitHub.
Создание OAuth-приложения на GitHub (шаг 4)
- Войдите в GitHub → Settings → Developer settings → OAuth Apps → Register a new application.
- Укажите Homepage URL: http://localhost:3000
- Authorization callback URL: http://localhost:3000/github-callback
- Зарегистрируйте приложение и сохраните CLIENT_ID и CLIENT_SECRET в .env.
Примечание: для production используйте HTTPS и корректный домен в callback.
Запрос данных пользователя
После получения access token вы можете запросить профиль:
const resp = await axios.get("https://api.github.com/user", {
headers: { Authorization: `token ${accessToken}` }
});
console.log(resp.data);Для получения email используйте /user/emails или указывайте дополнительные scope (например, user:email).
Рекомендации по безопасности
- Никогда не храните client_secret в репозиториях. Используйте переменные окружения или секретные менеджеры.
- Используйте state для защиты от CSRF.
- Применяйте HTTPS в production.
- Ограничьте время жизни токенов и поддерживайте возможность их отзыва.
- Храните токены в безопасных куках с флагами httpOnly и Secure или в серверной сессии.
- Валидируйте redirect_uri на стороне провайдера (GitHub требует совпадения).
Альтернативные подходы и библиотеки
- Passport.js — популярная стратегическая библиотека для Node.js с готовыми стратегиями для GitHub.
- simple-oauth2 — абстракция для работы с токенами OAuth на сервере.
- NextAuth — если используете Next.js, предоставляет готовые провайдеры и хранение сессий.
- OpenID Connect (OIDC) — если нужна аутентификация плюс профилизация и стандартные claims.
Выбор зависит от масштаба: для простых случаев достаточно собственного кода; для сложных сценариев лучше использовать проверенные библиотеки.
Когда этот подход не подходит
- Если нужно единое SSO для множества приложений — рассмотрите полноценный провайдер идентификации (Keycloak, Auth0).
- Если приложение — CLI или устройство без браузера, используйте device flow (GitHub поддерживает устройство, но оно в отдельной документации).
Частые ошибки и способы их устранения
- Ошибка: «No access token received» — проверьте корректность client_id и client_secret, а также заголовок Accept: application/json.
- Ошибка: «Invalid state» — проверьте хранение и сравнение state (cookie, session).
- Ошибка: CORS при запросах с фронта — не делайте обмен токена на клиенте; выполняйте обмен на серверной стороне.
Критерии приёмки
- Пользователь может авторизоваться через GitHub и увидеть профиль.
- CLIENT_SECRET не хранится в публичном репозитории.
- State защищает от CSRF и проверяется на callback.
- В продакшне callback использует HTTPS.
Чек-лист по ролям
Разработчик:
- Реализовать /auth и /github-callback
- Генерировать и проверять state
- Запросить профиль и сохранить пользователя
Безопасность:
- Проверить хранение секретов
- Настроить HTTPS
- Произвести ревью токенов и прав доступа (scope)
DevOps:
- Обеспечить секреты в CI/CD
- Настроить корректные переменные окружения на серверах
Продукт:
- Утвердить список scope и privacy-политику
- Решить, какие данные получать и хранить
Мини-методология внедрения
- Зарегистрировать приложение в GitHub и получить client_id/client_secret.
- Настроить окружение и базовый сервер Express.
- Реализовать роут /auth (с state) и /github-callback.
- Обменять код на access token на сервере.
- Запросить профиль и реализовать создание/вход пользователя в системе.
- Провести ревью безопасности и тестирование.
Тесты и сценарии приёмки
- При клике на /auth пользователь перенаправляется на GitHub.
- После согласия GitHub возвращает код и state; сервер корректно обменивал код на токен.
- Неверный state приводит к 400.
- При проблемах на стороне GitHub приложение возвращает информативную ошибку.
Риск-матрица и mitigations
- Утечка client_secret → mitigations: секреты в менеджерах, доступ по ролям, регулярная ротация.
- Перехват токена → mitigations: HTTPS, httpOnly cookie, короткий TTL.
- CSRF → mitigations: state, SameSite куки.
Заключение
OAuth 2.0 упрощает интеграцию авторизации через сторонние сервисы и позволяет избежать хранения пользовательских паролей. При правильной реализации (защита state, безопасное хранение секретов, HTTPS) интеграция становится безопасной и масштабируемой. Для простых проектов можно обойтись собственным минимальным кодом; для проектов с повышенными требованиями безопасности или множеством провайдеров стоит рассмотреть использование проверенных библиотек.
Важно: тестируйте поток в staging и проверяйте поведение при отказах провайдера.
Похожие материалы
Технологические новости: Facebook, Apple, Microsoft и другое
Нативное разрешение экрана на Mac — как узнать
Как отключить людей от Wi‑Fi и защитить роутер
Как объединить устройства HomeKit в комнаты, зоны и сцены
Меню «Дополнительные параметры» в Windows 11 — как использовать