Представьте себе: вы находитесь в эксклюзивном ночном клубе (назовём его «API Club»), и у входа стоит вышибала, проверяющий удостоверения личности, а внутри есть ещё один человек, контролирующий доступ в VIP-зоны. Этот вышибала — это аутентификация. Контролёр VIP-зон — это авторизация. И прекрасное взаимодействие между этими двумя концепциями — именно то, что мы сегодня рассмотрим на примере OAuth 2.0 и OpenID Connect.
Если вы когда-нибудь задумывались, почему вход в каждое приложение через вашу учётную запись Google работает так плавно, или как Spotify может получить доступ к вашим друзьям из Facebook, не украв секретный рецепт печенья вашей бабушки, то сейчас вы получите ответы. Пристегните ремни, потому что мы собираемся рассекретить один из самых элегантных протоколов безопасности в сети.
Сказка о двух протоколах
Начнём с очевидного: OAuth 2.0 и OpenID Connect не конкуренты — они больше похожи на динамичный дуэт. Представьте Бэтмена и Робина, но для веб-безопасности.
OAuth 2.0 — это система авторизации, которая обеспечивает безопасность наших цифровых ресурсов с 2012 года. Она сосредоточена на одном вопросе: «Может ли это приложение получить доступ к этому ресурсу?» Ей всё равно, кто вы; она заботится только о том, что вам разрешено делать.
OpenID Connect (OIDC), с другой стороны, — это уровень идентификации, который дополняет OAuth 2.0, как идеально сидящая шляпа. Он расширяет OAuth 2.0, чтобы ответить на вопрос: «Кто вы, собственно?»
Здесь становится интересно: пока OAuth 2.0 занимается частью «что вы можете получить доступ», OpenID Connect занимается частью «кто вы». Это как иметь вышибалу, который не только проверяет, можете ли вы войти в клуб, но и запоминает ваше имя на будущее.
OAuth 2.0: дирижёр авторизации
Понимание потока OAuth 2.0
OAuth 2.0 работает по простому принципу: никогда не делитесь своими учётными данными с третьими сторонами. Вместо этого он использует систему токенов, которые действуют как временные пропуска.
Давайте разберём это на практическом примере. Представьте, что вы создаёте приложение для обмена фотографиями, которое должно получить доступ к фотографиям пользователя в Google Drive:
Шаг 1: зарегистрируйте своё приложение
Сначала вам нужно зарегистрировать своё приложение в службе Google OAuth 2.0:
// Конфигурация для вашего клиента OAuth 2.0
const oauthConfig = {
clientId: 'your-google-client-id.googleusercontent.com',
clientSecret: 'your-client-secret', // Храните это в секрете!
redirectUri: 'https://yourapp.com/oauth/callback',
scope: 'https://www.googleapis.com/auth/drive.readonly'
};
Шаг 2: перенаправление пользователя на сервер авторизации
Когда пользователь хочет подключить свой Google Drive, перенаправьте его на конечную точку авторизации Google:
function initiateOAuthFlow() {
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` +
`client_id=${oauthConfig.clientId}&` +
`redirect_uri=${encodeURIComponent(oauthConfig.redirectUri)}&` +
`scope=${encodeURIComponent(oauthConfig.scope)}&` +
`response_type=code&` +
`state=${generateRandomState()}`; // Защита от CSRF
window.location.href = authUrl;
}
Шаг 3: обработка обратного вызова
Google перенаправляет обратно в ваше приложение с кодом авторизации:
// Конечная точка Express.js для обработки обратного вызова OAuth
app.get('/oauth/callback', async (req, res) => {
const { code, state } = req.query;
// Проверка параметра state для предотвращения атак CSRF
if (!verifyState(state)) {
return res.status(400).send('Неверный параметр state');
}
try {
// Обмен кода авторизации на токен доступа
const tokenResponse = await exchangeCodeForToken(code);
// Безопасное хранение токена доступа (база данных, сессия и т. д.)
await storeUserToken(req.user.id, tokenResponse.access_token);
res.redirect('/dashboard?connected=true');
} catch (error) {
res.status(500).send('Ошибка потока OAuth');
}
});
async function exchangeCodeForToken(code) {
const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: oauthConfig.clientId,
client_secret: oauthConfig.clientSecret,
code: code,
grant_type: 'authorization_code',
redirect_uri: oauthConfig.redirectUri,
}),
});
return await response.json();
}
Шаг 4: использование токена доступа
Теперь вы можете делать аутентифицированные запросы к Google Drive:
async function fetchUserPhotos(userId) {
const accessToken = await getUserToken(userId);
const response = await fetch('https://www.googleapis.com/drive/v3/files?q=mimeType contains "image/"', {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Accept': 'application/json',
},
});
if (response.status === 401) {
// Срок действия токена истёк, нужно обновить
await refreshAccessToken(userId);
return fetchUserPhotos(userId); // Повторная попытка с новым токеном
}
return await response.json();
}
OpenID Connect: шептун идентификации
Теперь давайте рассмотрим, как OAuth 2.0 получает обновление. OpenID Connect расширяет OAuth 2.0, добавляя токен идентификации, который содержит проверенную информацию о личности пользователя. Это не просто «вот доступ к их фотографиям» — это «вот кто они на самом деле».
Магия токенов идентификации
Пока OAuth 2.0 предоставляет токены доступа к ресурсам, OpenID Connect добавляет токены идентификации, которые содержат утверждения о пользователе:
// Пример полезной нагрузки токена идентификации (JWT)
{
"iss": "https://accounts.google.com",
"aud": "your-client-id.googleusercontent.com",
"sub": "1234567890", // Уникальный идентификатор пользователя
"name": "John Doe",
"email": "[email protected]",
"picture": "https://example.com/photo.jpg",
"iat": 1640995200,
"exp": 1640998800
}
Реализация аутентификации OpenID Connect
Давайте модернизируем наш пример OAuth 2.0 для использования OpenID Connect для аутентификации:
// Обновлённая конфигурация для OpenID Connect
const oidcConfig = {
clientId: 'your-google-client-id.googleusercontent.com',
clientSecret: 'your-client-secret',
redirectUri: 'https://yourapp.com/oidc/callback',
scope: 'openid email profile', // Обратите внимание на область 'openid'
issuer: 'https://accounts.google.com'
};
function initiateOIDCFlow() {
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` +
`client_id=${oidcConfig.clientId}&` +
`redirect_uri=${encodeURIComponent(oidcConfig.redirectUri)}&` +
`scope=${encodeURIComponent(oidcConfig.scope)}&` +
`response_type=code&` +
`state=${generateRandomState()}&` +
`nonce=${generateRandomNonce()}`; // Дополнительная безопасность для OIDC
window.location.href = auth