nestjs-vkid
v0.1.1
Published
NestJS module for VK ID OAuth 2.0 integration with PKCE support
Maintainers
Readme
nestjs-vkid
NestJS модуль для интеграции с VK ID OAuth 2.0 API с поддержкой PKCE (Proof Key for Code Exchange).
Описание
nestjs-vkid предоставляет удобный способ интеграции авторизации через VK ID в NestJS приложениях. Пакет включает в себя:
- Обмен кода авторизации на токены
- Обновление Access token через Refresh token
- Получение данных пользователя (немаскированных и публичных)
- Выход из аккаунта
- Отзыв разрешений доступа
- Утилиты для генерации PKCE параметров
- Полная типизация TypeScript
- Обработка всех ошибок API
Установка
npm install nestjs-vkidили
yarn add nestjs-vkidТребования
- NestJS 9.x или выше
- Node.js 16.x или выше
- TypeScript 4.5 или выше
Быстрый старт
1. Импорт модуля
import { Module } from '@nestjs/common';
import { VkIdModule } from 'nestjs-vkid';
@Module({
imports: [
VkIdModule.forRoot({
clientId: process.env.VK_ID_CLIENT_ID,
redirectUri: process.env.VK_ID_REDIRECT_URI,
}),
],
})
export class AppModule {}2. Использование сервиса
import { Injectable } from '@nestjs/common';
import { VkIdService } from 'nestjs-vkid';
@Injectable()
export class AuthService {
constructor(private readonly vkIdService: VkIdService) {}
async exchangeCode(code: string, codeVerifier: string, deviceId: string, state: string) {
const tokens = await this.vkIdService.exchangeCodeForTokens(
code,
codeVerifier,
deviceId,
state,
);
return tokens;
}
}Конфигурация
Синхронная конфигурация
VkIdModule.forRoot({
clientId: 'your-client-id',
redirectUri: 'https://your-site.com/callback',
baseUrl: 'https://id.vk.ru', // опционально, по умолчанию
})Асинхронная конфигурация
import { ConfigModule, ConfigService } from '@nestjs/config';
VkIdModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
clientId: configService.get('VK_ID_CLIENT_ID'),
redirectUri: configService.get('VK_ID_REDIRECT_URI'),
}),
inject: [ConfigService],
})Использование класса-фабрики
@Injectable()
class VkIdConfigService implements VkIdModuleOptionsFactory {
createVkIdOptions(): VkIdModuleOptions {
return {
clientId: process.env.VK_ID_CLIENT_ID,
redirectUri: process.env.VK_ID_REDIRECT_URI,
};
}
}
VkIdModule.forRootAsync({
useClass: VkIdConfigService,
})API Reference
VkIdService
exchangeCodeForTokens
Обмен кода авторизации на токены.
async exchangeCodeForTokens(
code: string,
codeVerifier: string,
deviceId: string,
state: string,
): Promise<VkIdTokens>Параметры:
code- Код авторизации, полученный после редиректаcodeVerifier- code_verifier, сгенерированный на фронтендеdeviceId- Идентификатор устройства, полученный вместе с кодомstate- Строка состояния, должна совпадать с отправленной
Возвращает:
{
access_token: string;
refresh_token: string;
id_token: string;
token_type: string;
expires_in: number;
user_id: number;
state: string;
scope: string;
}refreshAccessToken
Обновление Access token через Refresh token.
async refreshAccessToken(
refreshToken: string,
deviceId: string,
state: string,
scope?: string,
): Promise<VkIdRefreshTokens>Параметры:
refreshToken- Refresh token для обменаdeviceId- Идентификатор устройстваstate- Строка состоянияscope- Опциональный список прав доступа
Возвращает:
{
access_token: string;
refresh_token: string;
token_type: string;
expires_in: number;
user_id: number;
state: string;
scope: string;
}getUserInfo
Получение данных пользователя (немаскированных).
async getUserInfo(accessToken: string): Promise<VkIdUserInfo>Параметры:
accessToken- Access token пользователя
Возвращает:
{
user_id: string;
first_name: string;
last_name: string;
phone?: string;
email?: string;
avatar: string;
sex: 0 | 1 | 2;
verified: boolean;
birthday: string;
}getPublicUserInfo
Получение публичных (маскированных) данных пользователя.
async getPublicUserInfo(idToken: string): Promise<VkIdPublicUserInfo>Параметры:
idToken- ID token (JWT) пользователя
Возвращает:
{
user_id: string;
first_name: string;
last_name: string; // только первая буква
phone?: string; // маскированный
email?: string; // маскированный
avatar: string;
}logout
Выход из аккаунта.
async logout(accessToken: string): Promise<boolean>Параметры:
accessToken- Access token текущей сессии
Возвращает: true если выход выполнен успешно
revokePermissions
Отзыв разрешений доступа.
async revokePermissions(accessToken: string): Promise<boolean>Параметры:
accessToken- Access token пользователя
Возвращает: true если разрешения отозваны успешно
Утилиты
PKCE генератор
import { generateCodeVerifier, generateCodeChallenge, generatePkceParams, generateState } from 'nestjs-vkid';
// Генерация code_verifier
const codeVerifier = generateCodeVerifier();
// Генерация code_challenge
const codeChallenge = generateCodeChallenge(codeVerifier);
// Генерация полного набора PKCE параметров
const pkceParams = generatePkceParams();
// { codeVerifier, codeChallenge, codeChallengeMethod: 'S256' }
// Генерация state
const state = generateState();Валидаторы
import { validateCodeVerifier, validateState, validateRedirectUri } from 'nestjs-vkid';
// Валидация code_verifier
const isValid = validateCodeVerifier(codeVerifier);
// Валидация state
const isValidState = validateState(state);
// Валидация redirect_uri
const isValidUri = validateRedirectUri(uri, allowedUris);Примеры использования
Полный цикл авторизации
Frontend (генерация PKCE)
import { generatePkceParams, generateState } from 'nestjs-vkid';
// Генерация PKCE параметров
const { codeVerifier, codeChallenge } = generatePkceParams();
const state = generateState();
// Сохранение для последующего использования
sessionStorage.setItem('code_verifier', codeVerifier);
sessionStorage.setItem('state', state);
// Формирование URL авторизации
const authUrl = `https://id.vk.ru/authorize?response_type=code&client_id=${clientId}&scope=email%20phone&redirect_uri=${encodeURIComponent(redirectUri)}&state=${state}&code_challenge=${codeChallenge}&code_challenge_method=S256`;
// Перенаправление пользователя
window.location.href = authUrl;Backend (обмен кода на токены)
import { Controller, Post, Body } from '@nestjs/common';
import { VkIdService } from 'nestjs-vkid';
import { ExchangeCodeDto } from 'nestjs-vkid';
@Controller('auth')
export class AuthController {
constructor(private readonly vkIdService: VkIdService) {}
@Post('vk-id/exchange-code')
async exchangeCode(@Body() dto: ExchangeCodeDto) {
const tokens = await this.vkIdService.exchangeCodeForTokens(
dto.code,
dto.codeVerifier,
dto.deviceId,
dto.state,
);
// Сохранение токенов в базе данных
// await this.tokenService.saveTokens(tokens.user_id, tokens);
return {
success: true,
userId: tokens.user_id,
expiresIn: tokens.expires_in,
};
}
}Обновление токена
@Post('vk-id/refresh-token')
async refreshToken(@Body() dto: RefreshTokenDto) {
const tokens = await this.vkIdService.refreshAccessToken(
dto.refreshToken,
dto.deviceId,
dto.state,
dto.scope,
);
// Обновление токенов в базе данных
// await this.tokenService.updateTokens(tokens.user_id, tokens);
return {
success: true,
accessToken: tokens.access_token,
expiresIn: tokens.expires_in,
};
}Получение данных пользователя
@Get('vk-id/user-info')
async getUserInfo(@Body('accessToken') accessToken: string) {
const userInfo = await this.vkIdService.getUserInfo(accessToken);
return userInfo;
}
@Get('vk-id/public-user-info')
async getPublicUserInfo(@Body('idToken') idToken: string) {
const publicInfo = await this.vkIdService.getPublicUserInfo(idToken);
return publicInfo;
}Выход и отзыв разрешений
@Post('vk-id/logout')
async logout(@Body('accessToken') accessToken: string) {
const success = await this.vkIdService.logout(accessToken);
if (success) {
// Инвалидация токенов в базе данных
// await this.tokenService.invalidateTokens(accessToken);
}
return { success };
}
@Post('vk-id/revoke')
async revoke(@Body('accessToken') accessToken: string) {
const success = await this.vkIdService.revokePermissions(accessToken);
if (success) {
// Удаление токенов из базы данных
// await this.tokenService.deleteTokens(accessToken);
}
return { success };
}Обработка ошибок
Все ошибки API оборачиваются в VkIdException с соответствующими HTTP статусами:
import { VkIdException } from 'nestjs-vkid';
try {
const tokens = await this.vkIdService.exchangeCodeForTokens(...);
} catch (error) {
if (error instanceof VkIdException) {
console.error('VK ID Error:', error.errorCode);
console.error('Description:', error.errorDescription);
console.error('HTTP Status:', error.getStatus());
}
}Коды ошибок и HTTP статусы
invalid_token→ 401 Unauthorizedaccess_denied→ 403 Forbiddenserver_error→ 503 Service Unavailabletemporarily_unavailable→ 503 Service Unavailableslow_down→ 429 Too Many Requestsinvalid_client→ 400 Bad Requestlogin_required→ 401 Unauthorizedinteraction_required→ 400 Bad Request
DTO классы
Пакет предоставляет DTO классы с валидацией для использования в контроллерах:
import {
ExchangeCodeDto,
RefreshTokenDto,
GetUserInfoDto,
GetPublicUserInfoDto,
LogoutDto,
RevokeDto,
} from 'nestjs-vkid';Все DTO используют class-validator для автоматической валидации.
Типы
Пакет экспортирует все необходимые TypeScript типы:
import {
VkIdTokens,
VkIdRefreshTokens,
VkIdUserInfo,
VkIdPublicUserInfo,
VkIdErrorCode,
VkIdLanguage,
VkIdProvider,
VkIdScheme,
VkIdPrompt,
VkIdScope,
} from 'nestjs-vkid';Безопасность
⚠️ Важные рекомендации:
- PKCE обязателен - всегда используйте PKCE для защиты авторизации
- Генерация на фронтенде -
code_verifierдолжен генерироваться на клиенте - Валидация state - всегда проверяйте
stateдля предотвращения CSRF атак - Хранение токенов - храните токены на бэкенде, не передавайте их на фронтенд
- HTTPS - используйте HTTPS для всех запросов к VK ID API
- Не логируйте токены - никогда не логируйте токены в production
Лицензия
MIT
Поддержка
При возникновении проблем с API VK ID обращайтесь в поддержку: [email protected]
