OAuth 2.0 и JWT авторизация на TypeScript: полный гид для начинающих

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

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

Начать курс

OAuth 2.0 и JWT авторизация на TypeScript: для начинающих

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

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

Прежде чем писать код, важно понять теорию.

OAuth 2.0

Это протокол, который позволяет пользователю предоставить третьему приложению ограниченный доступ к своим ресурсам (например, к профилю на GitHub или Google) без передачи логина и пароля. В OAuth 2.0 есть несколько ролей:

  • Resource Owner — владелец ресурса (пользователь).
  • Client — приложение, которое хочет получить доступ.
  • Authorization Server — сервер, выдающий токены после успешной аутентификации.
  • Resource Server — сервер, хранящий защищенные данные.

Самый распространенный сценарий — "Authorization Code Flow", где пользователь перенаправляется на страницу провайдера, логинится, а затем сервер обменивает полученный код на токен.

JWT (JSON Web Token)

JWT — это строка в формате header.payload.signature, которая содержит данные (claims) и подписана секретным ключом. Она используется как Access Token (короткоживущий) или Refresh Token (долгоживущий) в OAuth 2.0. Преимущество JWT — он самодостаточен: сервер может проверить его без обращения к базе данных.

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

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

mkdir oauth-jwt-typescript
cd oauth-jwt-typescript
npm init -y
npm install express jsonwebtoken dotenv cors cookie-parser
npm install -D typescript @types/node @types/express @types/jsonwebtoken @types/cors @types/cookie-parser ts-node nodemon
npx tsc --init

В файле tsconfig.json раскомментируйте строку "outDir": "./dist" и установите "rootDir": "./src". Создайте структуру папок:

src/
  ├── index.ts
  ├── auth.ts
  ├── middleware.ts
  └── types.ts
.env

В файле .env укажите:

ACCESS_TOKEN_SECRET=your-secret-key-123
REFRESH_TOKEN_SECRET=your-refresh-secret-456
PORT=3000

Реализация сервера с OAuth 2.0 и JWT

1. Базовая структура сервера (index.ts)

Напишем простой Express-сервер с маршрутами для логина, получения токенов и защищенного эндпоинта.

import express from 'express';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import dotenv from 'dotenv';
import { login, refresh, logout } from './auth';
import { authenticateToken } from './middleware';

dotenv.config();

const app = express();
app.use(express.json());
app.use(cors());
app.use(cookieParser());

// Публичный маршрут
app.get('/', (req, res) => {
  res.send('OAuth 2.0 + JWT Server is running');
});

// Аутентификация (имитация OAuth 2.0)
app.post('/login', login);
app.post('/token', refresh);   // Обновление Access Token
app.delete('/logout', logout);

// Защищенный маршрут
app.get('/protected', authenticateToken, (req, res) => {
  res.json({ message: 'Доступ разрешен', user: (req as any).user });
});

app.listen(process.env.PORT, () => {
  console.log(`Server running on port ${process.env.PORT}`);
});

2. Типы данных (types.ts)

export interface TokenPayload {
  userId: number;
  username: string;
}

export interface User {
  id: number;
  username: string;
  password: string; // в реальном проекте здесь хранится хэш
}

3. Логика аутентификации (auth.ts)

Здесь мы эмулируем OAuth 2.0: после успешного логина генерируем Access и Refresh токены. Access Token живет 15 минут, Refresh — 7 дней.

import { Request, Response } from 'express';
import jwt from 'jsonwebtoken';
import { TokenPayload, User } from './types';

// Имитация базы данных
const users: User[] = [
  { id: 1, username: 'alice', password: 'password123' },
  { id: 2, username: 'bob', password: 'secret456' },
];

// Хранилище refresh токенов (в реальности — БД)
const refreshTokens: string[] = [];

export const login = (req: Request, res: Response) => {
  const { username, password } = req.body;
  const user = users.find(u => u.username === username && u.password === password);
  
  if (!user) {
    return res.status(401).json({ error: 'Неверные учетные данные' });
  }

  const payload: TokenPayload = { userId: user.id, username: user.username };
  
  const accessToken = jwt.sign(payload, process.env.ACCESS_TOKEN_SECRET!, { expiresIn: '15m' });
  const refreshToken = jwt.sign(payload, process.env.REFRESH_TOKEN_SECRET!, { expiresIn: '7d' });
  
  refreshTokens.push(refreshToken);
  
  res.cookie('refreshToken', refreshToken, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict',
    maxAge: 7 * 24 * 60 * 60 * 1000
  });
  
  res.json({ accessToken });
};

export const refresh = (req: Request, res: Response) => {
  const refreshToken = req.cookies.refreshToken;
  
  if (!refreshToken || !refreshTokens.includes(refreshToken)) {
    return res.status(403).json({ error: 'Refresh token недействителен' });
  }

  try {
    const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET!) as TokenPayload;
    const newAccessToken = jwt.sign(
      { userId: decoded.userId, username: decoded.username },
      process.env.ACCESS_TOKEN_SECRET!,
      { expiresIn: '15m' }
    );
    res.json({ accessToken: newAccessToken });
  } catch (err) {
    res.status(403).json({                

Блоги

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