OAuth клиент для TypeScript: интеграция авторизации в веб-приложениях

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

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

Начать курс

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

В современной веб-разработке безопасная аутентификация пользователей — одна из ключевых задач. OAuth 2.0 — это открытый протокол авторизации, который позволяет приложениям получать ограниченный доступ к ресурсам пользователя без передачи логина и пароля. В этой статье мы разберем, как создать OAuth клиент на TypeScript, который можно использовать в любом веб-приложении. Библиотека подходит для интеграции с Google, GitHub, Facebook и другими провайдерами.

Что такое OAuth клиент на TypeScript?

OAuth клиент — это набор функций и классов, которые реализуют поток авторизации (Authorization Code Flow) на стороне клиента или сервера. На TypeScript такой клиент обеспечивает строгую типизацию, что снижает количество ошибок и упрощает поддержку кода. Основные задачи клиента:

  • Генерация URL для перенаправления пользователя на страницу провайдера.
  • Обмен временного кода на access token и refresh token.
  • Хранение и обновление токенов.
  • Валидация параметров запроса (state, nonce).

Архитектура библиотеки

Библиотека состоит из нескольких модулей:

  • OAuthClient — основной класс, который управляет всеми этапами.
  • TokenStore — интерфейс для хранения токенов (можно реализовать в памяти, localStorage или базе данных).
  • ProviderConfig — конфигурация для конкретного провайдера (clientId, redirectUri, endpoints).
  • AuthError — класс для обработки ошибок авторизации.

Пример реализации на TypeScript

Ниже приведен базовый пример OAuth клиента для TypeScript. Сначала определим типы и интерфейсы:

// types.ts
export interface TokenResponse {
    access_token: string;
    token_type: string;
    expires_in: number;
    refresh_token?: string;
    scope?: string;
}

export interface ProviderConfig {
    authorizationEndpoint: string;
    tokenEndpoint: string;
    clientId: string;
    redirectUri: string;
    scope: string;
}

export interface TokenStore {
    getToken(): Promise<string | null>;
    setToken(token: TokenResponse): Promise<void>;
    clearToken(): Promise<void>;
}

Теперь создадим основной класс OAuthClient:

// OAuthClient.ts
import { TokenResponse, ProviderConfig, TokenStore } from './types';
import { AuthError } from './AuthError';

export class OAuthClient {
    private config: ProviderConfig;
    private store: TokenStore;

    constructor(config: ProviderConfig, store: TokenStore) {
        this.config = config;
        this.store = store;
    }

    // Генерация URL для авторизации
    getAuthorizationUrl(state: string): string {
        const params = new URLSearchParams({
            client_id: this.config.clientId,
            redirect_uri: this.config.redirectUri,
            response_type: 'code',
            scope: this.config.scope,
            state: state,
        });
        return `${this.config.authorizationEndpoint}?${params.toString()}`;
    }

    // Обмен кода на токен
    async exchangeCodeForToken(code: string, state: string, expectedState: string): Promise<TokenResponse> {
        if (state !== expectedState) {
            throw new AuthError('State mismatch. Possible CSRF attack.');
        }

        const body = new URLSearchParams({
            grant_type: 'authorization_code',
            code: code,
            redirect_uri: this.config.redirectUri,
            client_id: this.config.clientId,
        });

        const response = await fetch(this.config.tokenEndpoint, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: body,
        });

        if (!response.ok) {
            throw new AuthError('Failed to exchange code for token');
        }

        const tokenData: TokenResponse = await response.json();
        await this.store.setToken(tokenData);
        return tokenData;
    }

    // Обновление токена
    async refreshToken(): Promise<TokenResponse> {
        const token = await this.store.getToken();
        if (!token) {
            throw new AuthError('No token to refresh');
        }

        const refreshBody = new URLSearchParams({
            grant_type: 'refresh_token',
            refresh_token: token, // предполагаем, что хранится refresh_token
            client_id: this.config.clientId,
        });

        const response = await fetch(this.config.tokenEndpoint, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: refreshBody,
        });

        if (!response.ok) {
            throw new AuthError('Failed to refresh token');
        }

        const newToken: TokenResponse = await response.json();
        await this.store.setToken(newToken);
        return newToken;
    }

    // Проверка авторизации
    async isAuthenticated(): Promise<boolean> {
        const token = await this.store.getToken();
        return token !== null;
    }

    // Выход
    async logout(): Promise<void> {
        await this.store.clearToken();
    }
}

Интеграция с провайдером (пример Google)

Для использования клиента с Google OAuth 2.0 создайте конфигурацию и хранилище:

                
    
    

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