OAuth 2.0 и JWT авторизация на TypeScript: полное руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

OAuth 2.0 и JWT авторизация на TypeScript: полное руководство

Современные веб-приложения и API требуют надежных механизмов аутентификации и авторизации. Два наиболее популярных стандарта — OAuth 2.0 и JWT (JSON Web Tokens) — часто используются вместе для создания безопасных и масштабируемых систем. В этой статье мы разберем, как реализовать OAuth 2.0 и JWT авторизацию на TypeScript, используя Express.js и популярные библиотеки.

Что такое OAuth 2.0 и JWT?

OAuth 2.0 — это протокол авторизации, который позволяет приложениям получать ограниченный доступ к ресурсам пользователя без передачи его учетных данных. Он широко используется для входа через социальные сети (Google, GitHub, Facebook).

JWT (JSON Web Token) — это компактный и самодостаточный токен, который содержит информацию о пользователе и может быть проверен без обращения к базе данных. JWT часто используется как формат access-токенов в OAuth 2.0.

Вместе они образуют мощный механизм: OAuth 2.0 управляет процессом выдачи токенов, а JWT обеспечивает их безопасную передачу и верификацию.

Настройка проекта и установка зависимостей

Создадим новый проект на TypeScript. Убедитесь, что у вас установлен Node.js (версия 18+).

mkdir oauth-jwt-typescript
cd oauth-jwt-typescript
npm init -y

Установите необходимые зависимости:

npm install express jsonwebtoken passport passport-google-oauth20 dotenv bcryptjs
npm install -D typescript @types/express @types/jsonwebtoken @types/passport @types/passport-google-oauth20 @types/bcryptjs ts-node nodemon

Создайте файл tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"]
}

Структура проекта:

src/
  ├── config/
  │   └── passport.ts
  ├── controllers/
  │   └── authController.ts
  ├── middleware/
  │   └── authMiddleware.ts
  ├── models/
  │   └── user.ts
  ├── routes/
  │   └── authRoutes.ts
  ├── services/
  │   └── jwtService.ts
  └── app.ts

Реализация JWT-авторизации

JWT-токены состоят из трех частей: заголовок (header), полезная нагрузка (payload) и подпись (signature). Рассмотрим базовую реализацию на TypeScript.

Создадим сервис для работы с JWT (src/services/jwtService.ts):

import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';

dotenv.config();

const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET || 'access-secret-key';
const REFRESH_SECRET = process.env.JWT_REFRESH_SECRET || 'refresh-secret-key';

export interface TokenPayload {
  userId: string;
  email: string;
  role: string;
}

export const generateAccessToken = (payload: TokenPayload): string => {
  return jwt.sign(payload, ACCESS_SECRET, { expiresIn: '15m' });
};

export const generateRefreshToken = (payload: TokenPayload): string => {
  return jwt.sign(payload, REFRESH_SECRET, { expiresIn: '7d' });
};

export const verifyAccessToken = (token: string): TokenPayload => {
  return jwt.verify(token, ACCESS_SECRET) as TokenPayload;
};

export const verifyRefreshToken = (token: string): TokenPayload => {
  return jwt.verify(token, REFRESH_SECRET) as TokenPayload;
};

Создадим middleware для проверки JWT (src/middleware/authMiddleware.ts):

import { Request, Response, NextFunction } from 'express';
import { verifyAccessToken } from '../services/jwtService';

export interface AuthRequest extends Request {
  user?: {
    userId: string;
    email: string;
    role: string;
  };
}

export const authenticateJWT = (req: AuthRequest, res: Response, next: NextFunction): void => {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    res.status(401).json({ message: 'Токен не предоставлен' });
    return;
  }

  const token = authHeader.split(' ')[1];

  try {
    const decoded = verifyAccessToken(token);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(403).json({ message: 'Недействительный или просроченный токен' });
  }
};

Интеграция OAuth 2.0 (на примере Google)

Настроим Passport.js для аутентификации через Google. Создайте файл src/config/passport.ts:

import passport from 'passport';
import { Strategy as GoogleStrategy, Profile } from 'passport-google-oauth20';
import dotenv from 'dotenv';

dotenv.config();

passport.use(
  new GoogleStrategy(
    {
      clientID: process.env.GOOGLE_CLIENT_ID || '',
      clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
      callbackURL: 'http://localhost:3000/auth/google/callback',
    },
    async (accessToken: string, refreshToken: string, profile: Profile, done: Function) => {
      try {
        // Здесь можно найти или создать пользователя в БД
        const user = {
          googleId: profile.id,
          email: profile.emails?.[0]?.value || '',
          displayName: profile.displayName,
        };
        return done(null, user);
      } catch (error) {
        return done(error, undefined);
      }
    }
  )
);

passport.serializeUser((user: any, done) => {
  done(null, user);
});

passport.deserializeUser((user: any, done) => {
  done(null, user);
});

export default passport;

Создадим контроллер аутентификации (src/controllers/authController.ts):

import { Request, Response } from 'express';
import { generateAccessToken, generateRefreshToken, verifyRefreshToken } from '../services/jwtService';

export const googleCallback = (req: Request, res: Response): void => {
  const user = req.user as any;

  if (!user) {
    res.status(401).json({ message: 'Ошибка аутентификации' });
    return;
  }

  const tokenPayload = {
    userId: user.googleId,
    email: user.email,
    role: 'user',
  };

  const accessToken = generateAccessToken(tokenPayload);
  const refreshToken = generateRefreshToken(tokenPayload);

  res.json({
    message: 'Успешная аутентификация через Google',
    accessToken,
    refreshToken,
    user: {
      id: user.googleId,
      email: user.email,
      name: user.displayName,
    },
  });
};

export const refreshToken = (req: Request,                

Блоги

Рекомендаций по книгам