Skip to main content
Glama
hiltonbrown

Next.js MCP Server Template

by hiltonbrown
auth.ts4.52 kB
// Authentication utilities import { SignJWT, jwtVerify } from 'jose'; import { randomBytes, createHash } from 'crypto'; import { db } from './db'; const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET!); const XERO_CLIENT_ID = process.env.XERO_CLIENT_ID!; const XERO_CLIENT_SECRET = process.env.XERO_CLIENT_SECRET!; const XERO_REDIRECT_URI = process.env.XERO_REDIRECT_URI!; // JWT token functions export async function generateJWT(payload: any, expiresIn: string = '1h') { const jwt = await new SignJWT(payload) .setProtectedHeader({ alg: 'HS256' }) .setIssuedAt() .setExpirationTime(expiresIn) .sign(JWT_SECRET); return jwt; } export async function verifyJWT(token: string) { try { const { payload } = await jwtVerify(token, JWT_SECRET); return payload; } catch (error) { return null; } } // OAuth state parameter functions export function generateOAuthState() { return randomBytes(32).toString('hex'); } export async function storeOAuthState(state: string, accountId?: string) { // Create new state record await db.oAuthState.create({ data: { state, accountId, expiresAt: new Date(Date.now() + 10 * 60 * 1000) // 10 minutes } }); } export async function validateOAuthState(state: string) { const oauthState = await db.oAuthState.findUnique({ where: { state }, include: { account: true } }); if (oauthState && oauthState.expiresAt > new Date()) { // Delete the state after use await db.oAuthState.delete({ where: { id: oauthState.id } }); return oauthState.account; } return null; } // PKCE functions export function generatePKCEChallenge() { const verifier = randomBytes(32).toString('base64url'); const challenge = createHash('sha256') .update(verifier) .digest('base64url'); return { verifier, challenge }; } // Xero OAuth 2.0 flow export function getXeroAuthUrl(state: string, challenge: string) { const scopes = [ 'openid', 'profile', 'email', 'accounting.transactions', 'accounting.contacts', 'accounting.settings' ].join(' '); const params = new URLSearchParams({ response_type: 'code', client_id: XERO_CLIENT_ID, redirect_uri: XERO_REDIRECT_URI, scope: scopes, state: state, code_challenge: challenge, code_challenge_method: 'S256' }); return `https://login.xero.com/identity/connect/authorize?${params}`; } export async function exchangeCodeForTokens(code: string, verifier: string) { const tokenUrl = 'https://identity.xero.com/connect/token'; const params = new URLSearchParams({ grant_type: 'authorization_code', code: code, redirect_uri: XERO_REDIRECT_URI, code_verifier: verifier, client_id: XERO_CLIENT_ID, client_secret: XERO_CLIENT_SECRET }); const response = await fetch(tokenUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params }); if (!response.ok) { throw new Error(`Token exchange failed: ${response.statusText}`); } return await response.json(); } export async function refreshAccessToken(refreshToken: string) { const tokenUrl = 'https://identity.xero.com/connect/token'; const params = new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken, client_id: XERO_CLIENT_ID, client_secret: XERO_CLIENT_SECRET }); const response = await fetch(tokenUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params }); if (!response.ok) { throw new Error(`Token refresh failed: ${response.statusText}`); } return await response.json(); } // Session management for MCP connections export async function createMCPSession(accountId: string, tenantId?: string) { const sessionId = randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours const session = await db.mCPSession.create({ data: { sessionId, accountId, tenantId, expiresAt } }); return session; } export async function validateMCPSession(sessionId: string) { const session = await db.mCPSession.findUnique({ where: { sessionId }, include: { account: true } }); if (!session || session.expiresAt < new Date()) { return null; } return session; } export async function revokeMCPSession(sessionId: string) { await db.mCPSession.delete({ where: { sessionId } }); }

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/hiltonbrown/xero-mcp-with-next-js'

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