OAuth / авторизация

Как подключить вход и регистрацию на сайт через ВКонтакте: пошаговая инструкция

admin · 29.10.2026 · 7 мин чтения · 15
Оцените

Как подключить вход и регистрацию на сайт через ВКонтакте: пошаговая инструкция

Вход через ВКонтакте (VK ID) — один из самых популярных способов авторизации на русскоязычных сайтах. Он повышает конверсию регистрации, снижает количество "брошенных" форм и избавляет пользователя от необходимости придумывать пароль. В этой статье разберём, как подключить вход и регистрацию через ВК с нуля: от создания приложения до рабочего кода, который не заблокирует хостинг.


Что понадобится

  • Аккаунт ВКонтакте (желательно подтверждённый).

  • Сайт с HTTPS (VK ID требует защищённое соединение, кроме localhost).

  • Любой серверный язык (примеры будут на PHP и Node.js) — код обмена токена нельзя делать на клиенте, иначе выдадите секрет приложения.

  • Доступ к настройкам хостинга (для правки .htaccess/nginx при необходимости).


Шаг 1. Создаём приложение VK ID

  1. Перейдите в раздел управления приложениями: https://id.vk.com/about/business/go (ранее — vk.com/apps?act=manage).

  2. Нажмите «Создать приложение».

  3. Выберите тип «Веб-сайт» (Web).

  4. Укажите название, платформу и адрес сайта — например https://example.com.

  5. В настройках приложения найдите «Настройки» → «Адреса» и добавьте Redirect URI (доверенный redirect-адрес):
    https://example.com/auth/vk/callback

  6. Сохраните и скопируйте:

    • 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. Общая схема:

  1. Пользователь нажимает «Войти через ВК».

  2. Сайт генерирует code_verifier и его хеш code_challenge (PKCE), сохраняет verifier в сессии.

  3. Редирект на страницу авторизации VK ID с client_id, redirect_uri, code_challenge, state.

  4. Пользователь подтверждает доступ. VK возвращает на redirect_uri параметр code и state.

  5. Сервер меняет code + code_verifier + client_secret на access_token через backend-запрос.

  6. Сервер запрашивает профиль пользователя (id, имя, email при согласии).

  7. Сайт создаёт/находит пользователя в своей БД и выдаёт свою сессию.

Ключевой момент: обмен кода на токен идёт с сервера на сервер, поэтому секрет не утекает, а хостинг не блокирует запрос (это обычный HTTPS-вызов, а не браузерный CORS-запрос).


Шаг 3. Кнопка входа на сайте

html
<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
<?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
<?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:

js
// 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 не разделяет «регистрацию» и «вход» — это одна кнопка. Логика на вашей стороне:

php
// псевдокод
$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.ini allow_url_fopen = Off — тогда нужен curl;

  • если и curl заблокирован, напишите в поддержку хостинга — обычно whitelisting id.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).


Полезные ссылки


Итог

Подключение входа через ВК сводится к четырём действиям: создать приложение, реализовать /start с PKCE, реализовать /callback с обменом кода на токен на сервере и создать/найти пользователя в БД. Схема OAuth 2.1 + PKCE и server-to-server запросы — это как раз то, что не блокируется типичными хостингами и WAF: нет CORS, нет передачи секрета в браузер, все запросы — обычные HTTPS POST на id.vk.com и GET на api.vk.com. Если хостинг всё же упирается — whitelisting этих доменов или вынос обмена токена в отдельную функцию решает проблему за 10 минут.

Автор статьи
admin
7 статей
Профиль

Комментарии 0

Email не публикуется

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