Skip to main content
Glama

Industrial MCP Server

by intecrel
jwt.ts5.92 kB
/** * JWT Token Management for OAuth 2.1 Implementation * Handles token generation, validation, and claims management */ import { SignJWT, jwtVerify } from 'jose'; import { getOAuthConfig } from './config'; export interface TokenClaims { iss: string; // Issuer sub: string; // Subject (client_id) aud: string; // Audience (API identifier) exp: number; // Expiration time iat: number; // Issued at scope: string; // OAuth scopes client_id: string; // OAuth client identifier token_type: 'access_token' | 'authorization_code'; redirect_uri?: string; // For authorization codes code_challenge?: string; // For PKCE code_challenge_method?: string; // For PKCE [key: string]: any; // Index signature for JWT payload } export interface AccessTokenPayload { access_token: string; token_type: 'Bearer'; expires_in: number; scope: string; client_id: string; } /** * Generate JWT secret as Uint8Array for jose library */ const getJwtSecret = (): Uint8Array => { const config = getOAuthConfig(); return new TextEncoder().encode(config.jwtSecret); }; /** * Generate an access token for OAuth client */ export const generateAccessToken = async ( clientId: string, scopes: string[], userEmail?: string ): Promise<AccessTokenPayload> => { const config = getOAuthConfig(); const now = Math.floor(Date.now() / 1000); // Add 30-second buffer for clock skew and connection stability const clockSkewBuffer = 30; const claims: TokenClaims = { iss: config.issuer, sub: clientId, aud: config.issuer, // API identifier exp: now + config.accessTokenTtl, iat: now - clockSkewBuffer, // Issue token slightly in past for clock skew scope: scopes.join(' '), client_id: clientId, token_type: 'access_token', user_email: userEmail // Include user email for consent tracking }; const secret = getJwtSecret(); const accessToken = await new SignJWT(claims) .setProtectedHeader({ alg: config.jwtAlgorithm }) .sign(secret); return { access_token: accessToken, token_type: 'Bearer', expires_in: config.accessTokenTtl, scope: scopes.join(' '), client_id: clientId }; }; /** * Generate an authorization code for OAuth flow */ export const generateAuthorizationCode = async ( clientId: string, scopes: string[], redirectUri: string, codeChallenge?: string, codeChallengeMethod?: string, user?: any // Authenticated user info ): Promise<string> => { const config = getOAuthConfig(); const now = Math.floor(Date.now() / 1000); const claims = { iss: config.issuer, sub: clientId, aud: config.issuer, exp: now + config.authCodeTtl, iat: now, scope: scopes.join(' '), client_id: clientId, token_type: 'authorization_code' as const, redirect_uri: redirectUri, code_challenge: codeChallenge, code_challenge_method: codeChallengeMethod, // Include authenticated user info in the authorization code user_id: user?.id || user?.email || user?.sub, user_email: user?.email, auth_time: now, // When user authenticated }; const secret = getJwtSecret(); return await new SignJWT(claims) .setProtectedHeader({ alg: config.jwtAlgorithm }) .sign(secret); }; /** * Check if token is revoked using the token blacklist */ async function isTokenRevoked(token: string): Promise<boolean> { try { const { isTokenRevoked: checkRevoked } = await import('./token-blacklist'); return checkRevoked(token); } catch { return false; // If revocation check fails, don't block token validation } } /** * Validate and decode JWT token */ export const validateToken = async (token: string): Promise<TokenClaims> => { try { // Check if token is revoked first if (await isTokenRevoked(token)) { throw new Error('Token has been revoked'); } const config = getOAuthConfig(); const secret = getJwtSecret(); const { payload } = await jwtVerify(token, secret, { issuer: config.issuer, audience: config.issuer, }); return payload as unknown as TokenClaims; } catch (error) { throw new Error(`Invalid token: ${error instanceof Error ? error.message : 'Unknown error'}`); } }; /** * Extract Bearer token from Authorization header */ export const extractBearerToken = (authorizationHeader: string | null): string | null => { if (!authorizationHeader || !authorizationHeader.startsWith('Bearer ')) { return null; } return authorizationHeader.substring(7); // Remove "Bearer " prefix }; /** * Validate access token and return claims */ export const validateAccessToken = async (authorizationHeader: string | null): Promise<TokenClaims> => { const token = extractBearerToken(authorizationHeader); if (!token) { throw new Error('Missing or invalid Authorization header'); } const claims = await validateToken(token); if (claims.token_type !== 'access_token') { throw new Error('Invalid token type'); } const now = Math.floor(Date.now() / 1000); if (claims.exp < now) { throw new Error('Token expired'); } // Update last used timestamp for consent grant tracking try { const { updateLastUsed } = await import('./consent-grants-api'); if (claims.user_email && claims.client_id) { await updateLastUsed(claims.user_email, claims.client_id); } } catch { // Continue if consent grant update fails } return claims; }; /** * Generate a secure random string for client secrets, codes, etc. */ export const generateSecureRandomString = (length: number = 32): string => { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; let result = ''; const randomArray = new Uint8Array(length); crypto.getRandomValues(randomArray); for (let i = 0; i < length; i++) { result += chars[randomArray[i] % chars.length]; } return result; };

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/intecrel/industrial-mcp'

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