Question Details

No question body available.

Tags

typescript next.js calendar

Answers (2)

Accepted Answer Available
Accepted Answer
February 25, 2026 Score: 3 Rep: 167 Quality: High Completeness: 60%
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 { MICROSOFTOAUTHCONFIG, OAUTHENDPOINTS } from "../oauth";

export class MicrosoftCalendarProvider implements CalendarProvider {

constructor() {}

getAuthUrl(redirectUri: string, state?: string): string { const params = new URLSearchParams({ clientid: MICROSOFTOAUTHCONFIG.clientId, responsetype: "code", redirecturi: redirectUri, scope: MICROSOFTOAUTHCONFIG.scopes.join(" "), responsemode: "query", prompt: "consent", // Force consent to always return a refresh token ...(state && { state }), });

return ${OAUTHENDPOINTS.microsoft.auth}?${params.toString()}; }

async exchangeCodeForTokens(code: string, redirectUri: string): Promise { try { // Use direct OAuth2 token endpoint instead of MSAL // This gives us the actual refreshtoken string (MSAL hides it in its internal cache) const body = new URLSearchParams({ clientid: MICROSOFTOAUTHCONFIG.clientId, clientsecret: MICROSOFTOAUTHCONFIG.clientSecret, code, redirecturi: redirectUri, granttype: 'authorizationcode', scope: MICROSOFTOAUTHCONFIG.scopes.join(' '), });

const response = await fetch(OAUTHENDPOINTS.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.accesstoken) { throw new Error("No access token received from Microsoft"); }

const expiresAt = new Date(); if (data.expiresin) { expiresAt.setSeconds(expiresAt.getSeconds() + data.expiresin); } else { // Default to 1 hour if no expiry provided expiresAt.setHours(expiresAt.getHours() + 1); }

console.log('Microsoft token exchange successful', { hasAccessToken: !!data.accesstoken, hasRefreshToken: !!data.refreshtoken, expiresIn: data.expiresin, scope: data.scope, });

const tokenData = { accessToken: data.accesstoken, refreshToken: data.refreshtoken || 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 { 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 { try { // Use direct OAuth2 token endpoint for refresh const body = new URLSearchParams({ clientid: MICROSOFTOAUTHCONFIG.clientId, clientsecret: MICROSOFTOAUTHCONFIG.clientSecret, refreshtoken: refreshToken, granttype: 'refreshtoken', scope: MICROSOFTOAUTHCONFIG.scopes.join(' '), });

const response = await fetch(OAUTHENDPOINTS.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.accesstoken) { throw new Error("No access token received from Microsoft refresh"); }

const expiresAt = new Date(); if (data.expiresin) { expiresAt.setSeconds(expiresAt.getSeconds() + data.expiresin); } else { expiresAt.setHours(expiresAt.getHours() + 1); }

console.log('Microsoft token refresh successful', { hasNewAccessToken: !!data.accesstoken, hasNewRefreshToken: !!data.refreshtoken, expiresIn: data.expiresin, });

const tokenData = { accessToken: data.accesstoken, // 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.

February 25, 2026 Score: 3 Rep: 30 Quality: Low Completeness: 40%

I think your refresh token doesn't work efficiently.

You can fetch it by using this code:

const response = await fetch(OAUTH_ENDPOINTS.microsoft.token, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body.toString(), });