Skip to main content
Glama
JWTAuthImplementation.ts8.44 kB
/** * JWT Authentication Implementation * Complete JWT authentication with token refresh and RequestManager integration */ import type { AuthConfig, RequestOptions } from "@/types/client.js"; import { AuthenticationError } from "@/types/client.js"; import { AUTH_METHODS } from "@/types/wordpress.js"; import { LoggerFactory } from "@/utils/logger.js"; import type { RequestManager } from "./RequestManager.js"; export interface JWTTokenResponse { token: string; user_email: string; user_nicename: string; user_display_name: string; expires_in?: number; } export interface JWTValidateResponse { code: string; data: { status: number; }; } export interface JWTRefreshResponse { token: string; expires_in?: number; } export class JWTAuthImplementation { private logger = LoggerFactory.api(); private jwtToken: string | null = null; private tokenExpiry: number | null = null; private refreshTokenValue: string | null = null; constructor( private requestManager: RequestManager, private authConfig: AuthConfig, ) {} /** * Complete JWT authentication implementation */ async authenticateJWT(): Promise<void> { if (!this.authConfig.username || !this.authConfig.password) { throw new AuthenticationError("JWT authentication requires username and password", AUTH_METHODS.JWT); } try { this.logger.info("Starting JWT authentication", { username: this.authConfig.username, endpoint: "jwt-auth/v1/token", }); const response = await this.requestManager.request<JWTTokenResponse>( "POST", "jwt-auth/v1/token", { username: this.authConfig.username, password: this.authConfig.password, }, { skipAuth: true, // Don't use auth headers for the auth request itself timeout: 10000, } as RequestOptions & { skipAuth: boolean }, ); if (!response.token) { throw new AuthenticationError("JWT token not received in response", AUTH_METHODS.JWT); } // Store the JWT token this.jwtToken = response.token; // Calculate token expiry (default to 24 hours if not provided) const expiresInSeconds = response.expires_in || 24 * 60 * 60; this.tokenExpiry = Date.now() + expiresInSeconds * 1000; this.logger.info("JWT authentication successful", { user: response.user_nicename, expiresAt: new Date(this.tokenExpiry).toISOString(), }); // Test the token by validating it await this.validateToken(); } catch (error) { this.jwtToken = null; this.tokenExpiry = null; this.logger.error("JWT authentication failed", { error: error instanceof Error ? error.message : String(error), }); if (error instanceof Error) { throw new AuthenticationError(`JWT authentication failed: ${error.message}`, AUTH_METHODS.JWT); } throw new AuthenticationError("JWT authentication failed", AUTH_METHODS.JWT); } } /** * Validate JWT token */ async validateToken(): Promise<boolean> { if (!this.jwtToken) { return false; } try { const response = await this.requestManager.request<JWTValidateResponse>( "POST", "jwt-auth/v1/token/validate", {}, { headers: { Authorization: `Bearer ${this.jwtToken}`, }, timeout: 5000, }, ); const isValid = response.data.status === 200; if (!isValid) { this.logger.warn("JWT token validation failed", { response }); this.jwtToken = null; this.tokenExpiry = null; } return isValid; } catch (error) { this.logger.warn("JWT token validation error", { error: error instanceof Error ? error.message : String(error), }); return false; } } /** * Refresh JWT token */ async refreshToken(): Promise<void> { if (!this.jwtToken) { throw new AuthenticationError("No JWT token to refresh", AUTH_METHODS.JWT); } try { this.logger.debug("Refreshing JWT token"); // Some JWT implementations support refresh endpoints // First try the refresh endpoint if available try { const refreshResponse = await this.requestManager.request<JWTRefreshResponse>( "POST", "jwt-auth/v1/token/refresh", {}, { headers: { Authorization: `Bearer ${this.jwtToken}`, }, timeout: 5000, }, ); if (refreshResponse.token) { this.jwtToken = refreshResponse.token; const expiresInSeconds = refreshResponse.expires_in || 24 * 60 * 60; this.tokenExpiry = Date.now() + expiresInSeconds * 1000; this.logger.info("JWT token refreshed successfully", { expiresAt: new Date(this.tokenExpiry).toISOString(), }); return; } } catch (_refreshError) { this.logger.debug("JWT refresh endpoint not available, falling back to re-authentication"); } // Fallback: Re-authenticate with original credentials if (!this.authConfig.username || !this.authConfig.password) { throw new AuthenticationError("Cannot refresh JWT token: original credentials not available", AUTH_METHODS.JWT); } this.logger.debug("Re-authenticating to refresh JWT token"); await this.authenticateJWT(); } catch (error) { this.jwtToken = null; this.tokenExpiry = null; this.logger.error("JWT token refresh failed", { error: error instanceof Error ? error.message : String(error), }); throw new AuthenticationError( `JWT token refresh failed: ${error instanceof Error ? error.message : "Unknown error"}`, AUTH_METHODS.JWT, ); } } /** * Check if JWT token is expired */ isTokenExpired(): boolean { if (!this.tokenExpiry) { return true; } // Consider token expired if it expires within 5 minutes (300000ms) const buffer = 5 * 60 * 1000; return Date.now() >= this.tokenExpiry - buffer; } /** * Get current JWT token */ getToken(): string | null { return this.jwtToken; } /** * Get token expiry timestamp */ getTokenExpiry(): number | null { return this.tokenExpiry; } /** * Clear JWT token and expiry */ clearToken(): void { this.jwtToken = null; this.tokenExpiry = null; this.refreshTokenValue = null; this.logger.debug("JWT token cleared"); } /** * Get JWT auth headers */ getAuthHeaders(): Record<string, string> { if (!this.jwtToken) { return {}; } return { Authorization: `Bearer ${this.jwtToken}`, }; } /** * Check if we have a valid JWT token */ hasValidToken(): boolean { return this.jwtToken !== null && !this.isTokenExpired(); } /** * Automatic token refresh if needed */ async ensureValidToken(): Promise<void> { if (!this.jwtToken) { // No token, need to authenticate await this.authenticateJWT(); return; } if (this.isTokenExpired()) { // Token expired, need to refresh await this.refreshToken(); return; } // Token is still valid } /** * Logout and invalidate token */ async logout(): Promise<void> { if (!this.jwtToken) { return; } try { // Try to invalidate the token on the server await this.requestManager.request( "POST", "jwt-auth/v1/token/invalidate", {}, { headers: this.getAuthHeaders(), timeout: 5000, }, ); this.logger.info("JWT token invalidated on server"); } catch (error) { this.logger.warn("Failed to invalidate JWT token on server", { error: error instanceof Error ? error.message : String(error), }); } this.clearToken(); } /** * Get token information */ getTokenInfo(): { hasToken: boolean; isExpired: boolean; expiresAt: string | null; expiresIn: number | null; } { return { hasToken: this.jwtToken !== null, isExpired: this.isTokenExpired(), expiresAt: this.tokenExpiry ? new Date(this.tokenExpiry).toISOString() : null, expiresIn: this.tokenExpiry ? Math.max(0, this.tokenExpiry - Date.now()) : null, }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/docdyhr/mcp-wordpress'

If you have feedback or need assistance with the MCP directory API, please join our Discord server