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 создайте конфигурацию и хранилище:
Также в библиотеке
Рекомендаций