Microsoft Calendar Authorization Expiration Issue

1 day ago 2
ARTICLE AD BOX
import { Client } from "@microsoft/microsoft-graph-client"; import type { CalendarProvider, OAuthTokens, Calendar, ConnectionTestResult, UserInfo, Contact, CreateEventParams, UpdateEventParams, DeleteEventParams, SearchEventsParams, CreatedEvent } from "../types"; import { oauthTokensSchema, calendarSchema, microsoftCalendarListSchema, microsoftCalendarSchema, connectionTestResultSchema } from "../types"; import { MICROSOFT_OAUTH_CONFIG, OAUTH_ENDPOINTS } from "../oauth"; export class MicrosoftCalendarProvider implements CalendarProvider { constructor() {} getAuthUrl(redirectUri: string, state?: string): string { const params = new URLSearchParams({ client_id: MICROSOFT_OAUTH_CONFIG.clientId, response_type: "code", redirect_uri: redirectUri, scope: MICROSOFT_OAUTH_CONFIG.scopes.join(" "), response_mode: "query", prompt: "consent", // Force consent to always return a refresh token ...(state && { state }), }); return `${OAUTH_ENDPOINTS.microsoft.auth}?${params.toString()}`; } async exchangeCodeForTokens(code: string, redirectUri: string): Promise<OAuthTokens> { try { // Use direct OAuth2 token endpoint instead of MSAL // This gives us the actual refresh_token string (MSAL hides it in its internal cache) const body = new URLSearchParams({ client_id: MICROSOFT_OAUTH_CONFIG.clientId, client_secret: MICROSOFT_OAUTH_CONFIG.clientSecret, code, redirect_uri: redirectUri, grant_type: 'authorization_code', scope: MICROSOFT_OAUTH_CONFIG.scopes.join(' '), }); const response = await fetch(OAUTH_ENDPOINTS.microsoft.token, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body.toString(), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(`Token exchange failed: ${response.status} ${JSON.stringify(errorData)}`); } const data = await response.json(); if (!data.access_token) { throw new Error("No access token received from Microsoft"); } const expiresAt = new Date(); if (data.expires_in) { expiresAt.setSeconds(expiresAt.getSeconds() + data.expires_in); } else { // Default to 1 hour if no expiry provided expiresAt.setHours(expiresAt.getHours() + 1); } console.log('Microsoft token exchange successful', { hasAccessToken: !!data.access_token, hasRefreshToken: !!data.refresh_token, expiresIn: data.expires_in, scope: data.scope, }); const tokenData = { accessToken: data.access_token, refreshToken: data.refresh_token || undefined, // Real refresh token from OAuth response expiresAt, scope: data.scope || undefined, }; return oauthTokensSchema.parse(tokenData); } catch (error) { throw new Error(`Microsoft OAuth token exchange failed: ${error}`); } } async getUserInfo(accessToken: string): Promise<UserInfo> { try { const graphClient = Client.init({ authProvider: (done) => { done(null, accessToken); }, }); const userInfo = await graphClient .api("/me") .select("id,mail,userPrincipalName,displayName") .get(); // Microsoft may use either 'mail' or 'userPrincipalName' for email const email = userInfo.mail || userInfo.userPrincipalName; if (!email || !userInfo.id) { throw new Error("Required user information not available from Microsoft"); } return { email: email, name: userInfo.displayName || undefined, id: userInfo.id, }; } catch (error: any) { throw new Error(`Failed to fetch Microsoft user info: ${error.message}`); } } async refreshTokens(refreshToken: string): Promise<OAuthTokens> { try { // Use direct OAuth2 token endpoint for refresh const body = new URLSearchParams({ client_id: MICROSOFT_OAUTH_CONFIG.clientId, client_secret: MICROSOFT_OAUTH_CONFIG.clientSecret, refresh_token: refreshToken, grant_type: 'refresh_token', scope: MICROSOFT_OAUTH_CONFIG.scopes.join(' '), }); const response = await fetch(OAUTH_ENDPOINTS.microsoft.token, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body.toString(), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(`Token refresh failed: ${response.status} ${JSON.stringify(errorData)}`); } const data = await response.json(); if (!data.access_token) { throw new Error("No access token received from Microsoft refresh"); } const expiresAt = new Date(); if (data.expires_in) { expiresAt.setSeconds(expiresAt.getSeconds() + data.expires_in); } else { expiresAt.setHours(expiresAt.getHours() + 1); } console.log('Microsoft token refresh successful', { hasNewAccessToken: !!data.access_token, hasNewRefreshToken: !!data.refresh_token, expiresIn: data.expires_in, }); const tokenData = { accessToken: data.access_token, // Microsoft may rotate refresh tokens - use new one if provided, keep old otherwise refreshToken: data.refresh_token || refreshToken, expiresAt, scope: data.scope || undefined, }; return oauthTokensSchema.parse(tokenData); } catch (error) { throw new Error(`Microsoft token refresh failed: ${error}`); } }

Here is full solution.

Read Entire Article