Node.jsМодуль 6: Безопасность
OAuth 2.0
Вход через Google, GitHub, Passport.js
Цель урока
В этом уроке ты научишься:
- Понимать OAuth 2.0 flow
- Настраивать Passport.js
- Реализовывать вход через Google/GitHub
OAuth 2.0 Flow
- Пользователь нажимает "Войти через Google"
- Редирект на Google
- Пользователь разрешает доступ
- Google редиректит обратно с кодом
- Сервер обменивает код на токен
- Сервер получает данные пользователя
Passport.js
Установка
npm install passport passport-google-oauth20 passport-github2Настройка
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const GitHubStrategy = require('passport-github2').Strategy;
// Google Strategy
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/auth/google/callback'
}, async (accessToken, refreshToken, profile, done) => {
try {
let user = await User.findOne({ googleId: profile.id });
if (!user) {
user = await User.create({
googleId: profile.id,
email: profile.emails[0].value,
name: profile.displayName,
avatar: profile.photos[0]?.value
});
}
done(null, user);
} catch (error) {
done(error, null);
}
}));
// GitHub Strategy
passport.use(new GitHubStrategy({
clientID: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
callbackURL: '/auth/github/callback'
}, async (accessToken, refreshToken, profile, done) => {
try {
let user = await User.findOne({ githubId: profile.id });
if (!user) {
user = await User.create({
githubId: profile.id,
email: profile.emails?.[0]?.value,
name: profile.displayName || profile.username,
avatar: profile.photos[0]?.value
});
}
done(null, user);
} catch (error) {
done(error, null);
}
}));Маршруты
const express = require('express');
const passport = require('passport');
const jwt = require('jsonwebtoken');
const app = express();
app.use(passport.initialize());
// Google
app.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] })
);
app.get('/auth/google/callback',
passport.authenticate('google', { session: false }),
(req, res) => {
const token = jwt.sign(
{ id: req.user._id },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
// Редирект на фронтенд с токеном
res.redirect(`${process.env.FRONTEND_URL}/auth/callback?token=${token}`);
}
);
// GitHub
app.get('/auth/github',
passport.authenticate('github', { scope: ['user:email'] })
);
app.get('/auth/github/callback',
passport.authenticate('github', { session: false }),
(req, res) => {
const token = jwt.sign(
{ id: req.user._id },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
res.redirect(`${process.env.FRONTEND_URL}/auth/callback?token=${token}`);
}
);Модель пользователя
const userSchema = new mongoose.Schema({
email: { type: String, unique: true, sparse: true },
password: { type: String, select: false },
name: String,
avatar: String,
googleId: { type: String, unique: true, sparse: true },
githubId: { type: String, unique: true, sparse: true }
});Получение credentials
- Перейди на Google Cloud Console
- Создай проект
- APIs & Services → Credentials
- Create Credentials → OAuth client ID
- Добавь Authorized redirect URI:
http://localhost:3000/auth/google/callback
GitHub
- Перейди в Settings → Developer settings → OAuth Apps
- New OAuth App
- Authorization callback URL:
http://localhost:3000/auth/github/callback
.env
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret
GITHUB_CLIENT_ID=your-client-id
GITHUB_CLIENT_SECRET=your-client-secret
FRONTEND_URL=http://localhost:5173Практика
Задание: Связывание аккаунтов
Задача: Позволь пользователю связать Google и GitHub.
Решение:
// Middleware для связывания
function linkAccount(provider) {
return async (accessToken, refreshToken, profile, done) => {
try {
const providerIdField = `${provider}Id`;
// Если пользователь авторизован, связываем
if (this.req.user) {
const user = await User.findByIdAndUpdate(
this.req.user.id,
{ [providerIdField]: profile.id },
{ new: true }
);
return done(null, user);
}
// Иначе ищем или создаём
let user = await User.findOne({ [providerIdField]: profile.id });
if (!user) {
user = await User.create({
[providerIdField]: profile.id,
email: profile.emails?.[0]?.value,
name: profile.displayName
});
}
done(null, user);
} catch (error) {
done(error, null);
}
};
}Проверь себя
- Что такое OAuth 2.0?
- Зачем нужен callback URL?
- Как связать несколько провайдеров?