OAuth client for TypeScript: implementing authorization on an educational website
In modern web development, OAuth (Open Authorization) has become the de facto standard for authenticating users through third-party services. For the educational website pythonlib.ru, we have prepared a description of a universal OAuth client in TypeScript that allows implementing login via GitHub, Google, and other popular providers.
What is an OAuth client?
An OAuth client is a software component that manages the process of obtaining permissions from a user to access their data on a third-party service. In the context of TypeScript, such a client typically implements the OAuth 2.0 protocol and includes:
- Initialization of the authorization process
- Handling callback requests
- Exchanging an authorization code for an access token
- Token refresh (refresh token)
- Working with session state
Architecture of an OAuth client in TypeScript
Our client is built on the principles of modularity and type safety. Main components:
1. Provider configuration
interface OAuthProviderConfig { clientId: string; clientSecret: string; redirectUri: string; authorizationEndpoint: string; tokenEndpoint: string; scopes: string[];}
const githubConfig: OAuthProviderConfig = { clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, redirectUri: 'https://pythonlib.ru/auth/callback/github', authorizationEndpoint: 'https://github.com/login/oauth/authorize', tokenEndpoint: 'https://github.com/login/oauth/access_token', scopes: ['read:user', 'user:email']};2. Generating the authorization URL
function generateAuthUrl(config: OAuthProviderConfig, state: string): string { const params = new URLSearchParams({ client_id: config.clientId, redirect_uri: config.redirectUri, scope: config.scopes.join(' '), state: state, response_type: 'code' }); return `${config.authorizationEndpoint}?${params.toString()}`;}3. Exchanging the code for a token
async function exchangeCodeForToken( config: OAuthProviderConfig, code: string): Promise<OAuthTokenResponse> { const response = await fetch(config.tokenEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ client_id: config.clientId, client_secret: config.clientSecret, code: code, redirect_uri: config.redirectUri }) });
if (!response.ok) { throw new Error(`Token exchange failed: ${response.statusText}`); }
return response.json();}Working with tokens
After obtaining tokens, it is necessary to ensure their secure storage and refresh. It is recommended to use httpOnly cookies or a server-side session store.
Refreshing the access token
async function refreshAccessToken( config: OAuthProviderConfig, refreshToken: string): Promise<OAuthTokenResponse> { const response = await fetch(config.tokenEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ client_id: config.clientId, client_secret: config.clientSecret, refresh_token: refreshToken, grant_type: 'refresh_token' }) });
return response.json();}Complete implementation example for GitHub OAuth
import { randomBytes } from 'crypto';
class GitHubOAuthClient { private config: OAuthProviderConfig; private stateStore: Map<string, string> = new Map();
constructor() { this.config = { clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, redirec