Как подключить вход и регистрацию на сайт через ВКонтакте: пошаговая инструкция
Вход через ВКонтакте (VK ID) — один из самых популярных способов авторизации на русскоязычных сайтах. Он повышает конверсию регистрации, снижает количество "брошенных" форм и избавляет пользователя от необходимости придумывать пароль. В этой статье разберём, как подключить вход и регистрацию через ВК с нуля: от создания приложения до рабочего кода, который не заблокирует хостинг.
Что понадобится
-
Аккаунт ВКонтакте (желательно подтверждённый).
-
Сайт с HTTPS (VK ID требует защищённое соединение, кроме localhost).
-
Любой серверный язык (примеры будут на PHP и Node.js) — код обмена токена нельзя делать на клиенте, иначе выдадите секрет приложения.
-
Доступ к настройкам хостинга (для правки
.htaccess/nginx при необходимости).
Шаг 1. Создаём приложение VK ID
-
Перейдите в раздел управления приложениями: https://id.vk.com/about/business/go (ранее —
vk.com/apps?act=manage). -
Нажмите «Создать приложение».
-
Выберите тип «Веб-сайт» (Web).
-
Укажите название, платформу и адрес сайта — например
https://example.com. -
В настройках приложения найдите «Настройки» → «Адреса» и добавьте Redirect URI (доверенный redirect-адрес):
https://example.com/auth/vk/callback -
Сохраните и скопируйте:
-
ID приложения (Client ID);
-
Защищённый ключ (Client Secret).
-
⚠️ Секретный ключ храните только на сервере (в переменных окружения), никогда не в JS-коде и не в публичном репозитории.
Документация: https://id.vk.com/about/business/go/docs
Шаг 2. Как работает схема авторизации (OAuth 2.0 + PKCE)
Современный VK ID использует OAuth 2.1 с PKCE, это безопаснее старого implicit flow. Общая схема:
-
Пользователь нажимает «Войти через ВК».
-
Сайт генерирует
code_verifierи его хешcode_challenge(PKCE), сохраняет verifier в сессии. -
Редирект на страницу авторизации VK ID с
client_id,redirect_uri,code_challenge,state. -
Пользователь подтверждает доступ. VK возвращает на
redirect_uriпараметрcodeиstate. -
Сервер меняет
code+code_verifier+client_secretна access_token через backend-запрос. -
Сервер запрашивает профиль пользователя (id, имя, email при согласии).
-
Сайт создаёт/находит пользователя в своей БД и выдаёт свою сессию.
Ключевой момент: обмен кода на токен идёт с сервера на сервер, поэтому секрет не утекает, а хостинг не блокирует запрос (это обычный HTTPS-вызов, а не браузерный CORS-запрос).
Шаг 3. Кнопка входа на сайте
<a href="/auth/vk/start" class="btn-vk"> Войти через ВКонтакте </a>
Стилизуйте под брендбук VK (документация по кнопкам: https://id.vk.com/about/business/go/docs).
Шаг 4. Backend: старт авторизации (PHP)
Файл auth/vk/start.php:
<?php session_start(); $clientId = getenv('VK_CLIENT_ID'); $redirectUri = 'https://example.com/auth/vk/callback'; // Генерируем PKCE $codeVerifier = bin2hex(random_bytes(32)); // 64 символа $codeChallenge = rtrim(strtr(base64_encode(hash('sha256', $codeVerifier, true)), '+/', '-_'), '='); $state = bin2hex(random_bytes(16)); $_SESSION['vk_code_verifier'] = $codeVerifier; $_SESSION['vk_state'] = $state; $params = http_build_query([ 'response_type' => 'code', 'client_id' => $clientId, 'redirect_uri' => $redirectUri, 'scope' => 'email', // при необходимости: phone 'state' => $state, 'code_challenge' => $codeChallenge, 'code_challenge_method' => 'S256', 'v' => '5.199', // версия API ]); header('Location: https://id.vk.com/authorize?' . $params); exit;
Шаг 5. Backend: обработка callback (PHP)
Файл auth/vk/callback.php:
<?php session_start(); $clientId = getenv('VK_CLIENT_ID'); $clientSecret = getenv('VK_CLIENT_SECRET'); $redirectUri = 'https://example.com/auth/vk/callback'; // 1. Проверка state (защита от CSRF) if (!isset($_GET['state'], $_SESSION['vk_state']) || !hash_equals($_SESSION['vk_state'], $_GET['state'])) { http_response_code(400); exit('Invalid state'); } if (empty($_GET['code'])) { http_response_code(400); exit('Authorization code missing'); } $code = $_GET['code']; $deviceId = $_GET['device_id'] ?? ''; // VK может вернуть device_id $codeVerifier = $_SESSION['vk_code_verifier'] ?? ''; unset($_SESSION['vk_state'], $_SESSION['vk_code_verifier']); // 2. Обмен кода на access_token (server-to-server) $tokenUrl = 'https://id.vk.com/oauth2/auth'; $post = http_build_query([ 'grant_type' => 'authorization_code', 'code' => $code, 'client_id' => $clientId, 'client_secret' => $clientSecret, 'redirect_uri' => $redirectUri, 'code_verifier' => $codeVerifier, 'device_id' => $deviceId, ]); $ch = curl_init($tokenUrl); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => $post, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15, CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'], ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200 || !$response) { http_response_code(502); exit('Token exchange failed'); } $tokenData = json_decode($response, true); $accessToken = $tokenData['access_token'] ?? null; $userId = $tokenData['user_id'] ?? null; if (!$accessToken) { http_response_code(502); exit('No access_token'); } // 3. Получаем профиль пользователя $profileUrl = 'https://api.vk.com/method/users.get?' . http_build_query([ 'user_ids' => $userId, 'fields' => 'first_name,last_name,photo_100', 'access_token' => $accessToken, 'v' => '5.199', ]); $profileJson = file_get_contents($profileUrl); $profile = json_decode($profileJson, true)['response'][0] ?? null; if (!$profile) { http_response_code(502); exit('Profile fetch failed'); } // 4. Здесь создаём/находим пользователя в своей БД и открываем сессию // $user = findOrCreateUser($profile['id'], $profile['first_name'], ...); $_SESSION['user'] = [ 'vk_id' => $profile['id'], 'first_name'=> $profile['first_name'], 'last_name' => $profile['last_name'], 'photo' => $profile['photo_100'] ?? null, ]; header('Location: /profile'); exit;
Шаг 6. Тот же код на Node.js (Express)
Если у вас Node.js, удобно использовать express + axios:
// app.js const express = require('express'); const axios = require('axios'); const crypto = require('crypto'); const session = require('express-session'); const app = express(); app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: true, cookie: { httpOnly: true, secure: true, sameSite: 'lax' } })); const CLIENT_ID = process.env.VK_CLIENT_ID; const CLIENT_SECRET = process.env.VK_CLIENT_SECRET; const REDIRECT_URI = 'https://example.com/auth/vk/callback'; app.get('/auth/vk/start', (req, res) => { const verifier = crypto.randomBytes(32).toString('hex'); const challenge = crypto .createHash('sha256') .update(verifier) .digest('base64') .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); const state = crypto.randomBytes(16).toString('hex'); req.session.vkVerifier = verifier; req.session.vkState = state; const params = new URLSearchParams({ response_type: 'code', client_id: CLIENT_ID, redirect_uri: REDIRECT_URI, scope: 'email', state, code_challenge: challenge, code_challenge_method: 'S256', v: '5.199' }); res.redirect('https://id.vk.com/authorize?' + params.toString()); }); app.get('/auth/vk/callback', async (req, res) => { const { code, state, device_id } = req.query; if (!state || state !== req.session.vkState) return res.status(400).send('Invalid state'); const verifier = req.session.vkVerifier; delete req.session.vkState; delete req.session.vkVerifier; try { const body = new URLSearchParams({ grant_type: 'authorization_code', code, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, redirect_uri: REDIRECT_URI, code_verifier: verifier, device_id: device_id || '' }); const { data } = await axios.post('https://id.vk.com/oauth2/auth', body.toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, timeout: 15000 }); const { access_token, user_id } = data; const profile = await axios.get('https://api.vk.com/method/users.get', { params: { user_ids: user_id, fields: 'first_name,last_name,photo_100', access_token, v: '5.199' }, timeout: 15000 }); const user = profile.data.response[0]; // TODO: найти или создать пользователя в БД req.session.user = { vk_id: user.id, first_name: user.first_name, last_name: user.last_name }; res.redirect('/profile'); } catch (err) { console.error(err.response?.data || err.message); res.status(502).send('Ошибка авторизации'); } }); app.listen(3000);
Установка зависимостей: npm i express axios express-session.
Шаг 7. Регистрация vs вход
VK ID не разделяет «регистрацию» и «вход» — это одна кнопка. Логика на вашей стороне:
// псевдокод $user = db_find_user_by_vk_id($vkId); if (!$user) { $user = db_create_user([ 'vk_id' => $vkId, 'first_name' => $profile['first_name'], 'last_name' => $profile['last_name'], 'email' => $emailFromToken ?? null, // если запросили scope=email 'created_at' => date('Y-m-d H:i:s') ]); } open_user_session($user['id']);
Если email обязателен, а VK его не вернул (пользователь отказал), покажите форму «Подтвердите почту» после авторизации.
Шаг 8. Возможные проблемы и как их обойти (чтобы хостинг не блокировал)
1. «CORS-ошибка» при обмене кода
Не делайте обмен кода на токен из браузера. Всегда через backend. Тогда CORS не применяется — это обычный серверный HTTPS-запрос, shared-хостинги его не блокируют.
2. Хостинг блокирует curl/file_get_contents
Некоторые дешёвые хостинги запрещают внешние HTTP-запросы. Признаки: curl_exec возвращает false, ошибка 403/0. Решения:
-
используйте
file_get_contentsс контекстом илиwp_remote_post(если WordPress); -
проверьте, не стоит ли в
php.iniallow_url_fopen = Off— тогда нуженcurl; -
если и
curlзаблокирован, напишите в поддержку хостинга — обычно whitelistingid.vk.comиapi.vk.comрешает вопрос; -
альтернатива — вынести обмен токена на отдельный микросервис/Serverless-функцию.
3. Хостинг режет Authorization заголовок
Мы не используем заголовок Authorization, а передаём access_token параметром — это как раз обходит типичный фильтр ModSecurity.
4. ModSecurity блокирует callback
Параметры code, state, code_verifier могут восприниматься WAF как подозрительные. Если получаете 403 от хостинга — попросите добавить id.vk.com в whitelist либо используйте отдельный поддомен с ослабленными правилами.
5. Нет HTTPS
VK ID не работает по HTTP (кроме localhost). Подключите бесплатный Let's Encrypt через панель хостинга — обычно в 1 клик.
6. Cookies с SameSite=Lax при редиректе
При возврате с id.vk.com cookie сессии должна отправляться. Ставьте SameSite=Lax (не Strict) — иначе state в сессии не найдётся.
7. Ошибка invalid_client
Проверьте, что client_secret из настроек именно вашего приложения, а redirect_uri в коде побайтово совпадает с указанным в кабинете VK (включая слэш на конце).
Шаг 9. Безопасность: чек-лист
-
✅
client_secretтолько в env-переменных. -
✅ Проверка
state(hash_equals, не===). -
✅ PKCE
S256(неplain). -
✅ Сессия после логина перегенерируется:
session_regenerate_id(true). -
✅ Таймауты на HTTP-запросы (10–15 с).
-
✅ Логирование ошибок без вывода секретов в браузер.
-
✅ Ограничение частоты попыток логина (rate limit).
Полезные ссылки
-
Портал VK ID для разработчиков: https://id.vk.com/about/business/go
-
Документация VK ID: https://id.vk.com/about/business/go/docs
-
Метод
users.get: https://dev.vk.com/ru/method/users.get -
OAuth 2.0 PKCE (RFC 7636): https://datatracker.ietf.org/doc/html/rfc7636
-
VK API версии: https://dev.vk.com/ru/reference/versions
Итог
Подключение входа через ВК сводится к четырём действиям: создать приложение, реализовать /start с PKCE, реализовать /callback с обменом кода на токен на сервере и создать/найти пользователя в БД. Схема OAuth 2.1 + PKCE и server-to-server запросы — это как раз то, что не блокируется типичными хостингами и WAF: нет CORS, нет передачи секрета в браузер, все запросы — обычные HTTPS POST на id.vk.com и GET на api.vk.com. Если хостинг всё же упирается — whitelisting этих доменов или вынос обмена токена в отдельную функцию решает проблему за 10 минут.




Комментарии 0
Комментариев пока нет. Будьте первым!