Microsoft Outlook

Configure Microsoft Outlook calendar OAuth for Recall so users can sync meetings and enable auto-recording with calendar v2

Prerequisites

  • For local development and testing, make sure you set up an ngrok static URL​ which you can put inside the URI on step 3 below. Once you are ready for production, you can add your production URL to the list of redirect URIs.

Creating Microsoft OAuth App

Create a new Microsoft OAuth App:

  1. Sign into Azure and navigate to Entra > App Registration

  2. Click New Registration and select a name for your app. This will be user-facing.

    Under the "Supported account types" section, select:"Any Entra ID Tenant + Personal Microsoft accounts"

  3. For the Redirect URI, choose Web as the platform type. Then add the redirect URI which will be the URL the user is redirected to after they authorize your app via OAuth 2.0.

📘

You must verify ownership of the domain used for the Authorized redirect URI. Domain verification is required when publishing your OAuth 2.0 in production.

This URL must not include Recall's domain and should originate from your own app.

(Note: For development purpose, see below for moving app to production/submitting for verification)

  1. Create a new client secret for the application. Ensure to copy the secret value as this will be needed in a later step. We highly recommend choosing the longest feasible time for expiry (e.g. 24 months).

  1. Add the "Calendars.Read" API permission for the app under Microsoft Graph > Delegated Permissions > Calendars.Read

  1. Grab the Application(client) ID from the overview tab.

Implement OAuth 2.0 Authorization Code Flow

First, generate an authorization URL with the following code and add this to a button in your dashboard:

/**
 * Generate a Microsoft Outlook OAuth URL for the user.
 * You can pass a custom state object to the URL to be returned in the callback.
 */
function generate_outlook_calendar_oauth_url(): URL {
    const params = {
        client_id: env.OUTLOOK_OAUTH_CLIENT_ID!,
        redirect_uri: env.OUTLOOK_OAUTH_REDIRECT_URI!,
        response_type: "code",
        scope: "offline_access openid email https://graph.microsoft.com/Calendars.Read",
        prompt: "consent",
        state: Buffer.from(JSON.stringify({ platform: "microsoft_outlook" })).toString("base64"),
    };

    const url = new URL("https://login.microsoftonline.com/common/oauth2/v2.0/authorize");
    url.search = new URLSearchParams(params).toString();

    return url;
}

See the Microsoft Graph API docs for more info about creating the authorization URL

After the user compeltes the authorization code flow, the Microsoft will call your OAuth callback URL and hit your endpoint with an authorization code. You will need to trade the authorization code for the users refresh token from microsoft

const OUTLOOK_OAUTH_CLIENT_ID = ""
const OUTLOOK_OAUTH_CLIENT_SECRET = ""

type CalendarType = {
    id: string;
    platform_email: string | null;
    oauth_client_id: string | null;
    oauth_client_secret: string | null;
    oauth_refresh_token: string | null;
    platform: string;
    status_changes: { created_at: string; status: string }[];
    created_at: string;
    updated_at: string;
}

/**
 * Retrieve the OAuth tokens from the authorization code once the user has authorized their calendar.
 * Create a calendar for this user in Recall.
 */
export async function calendar_oauth_callback(args: {
    code: string,
    state: string,
}): Promise<{ calendar: CalendarType }> {
    const { code: authorization_code, state: raw_state } = args;

    const { platform } =JSON.parse(Buffer.from(raw_state, "base64").toString("utf8"));

    console.log(`Received authorization code: ${authorization_code} and state: ${raw_state}`);

    const oauth_tokens = await retrieve_outlook_calendar_oauth_tokens({ authorization_code });
    
    console.log(`Successfully retrieved Outlook Calendar OAuth tokens: ${JSON.stringify(oauth_tokens)}`);
    const calendar_config = {
        platform: "microsoft_outlook",
        oauth_client_id: OUTLOOK_OAUTH_CLIENT_ID,
        oauth_client_secret: OUTLOOK_OAUTH_CLIENT_SECRET,
        oauth_refresh_token: oauth_tokens.refresh_token,
        platform_email: oauth_tokens.platform_email,
    };

    if (!calendar_config?.oauth_refresh_token) throw new Error("No calendar config retrieved");

    // ... create calendar
}

/**
 * Retrieve the OAuth tokens from the authorization code.
 * Once the user has authorized their calendar, we can use the authorization code to retrieve the OAuth tokens.
 */
async function retrieve_outlook_calendar_oauth_tokens(args: {
    authorization_code: string,
}): Promise<{ access_token: string, refresh_token: string, expires_in: number, platform_email: string }> {
    const { authorization_code } = z.object({ authorization_code: z.string() }).parse(args);

    // Get the OAuth tokens from the authorization code.
    const params = {
        client_id: env.OUTLOOK_OAUTH_CLIENT_ID!,
        client_secret: env.OUTLOOK_OAUTH_CLIENT_SECRET!,
        redirect_uri: env.OUTLOOK_OAUTH_REDIRECT_URI!,
        grant_type: "authorization_code",
        code: authorization_code,
    };
    const response = await fetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", {
        method: "POST",
        body: new URLSearchParams(params),
    });
    if (!response.ok) throw new Error(await response.text());

    const oauth_tokens = z.object({
        access_token: z.string(),
        refresh_token: z.string(),
        expires_in: z.number(),
    }).parse(await response.json());

    // Get the user's email from the OIDC userinfo endpoint (uses the 'email' scope).
    const userinfo_response = await fetch("https://graph.microsoft.com/oidc/userinfo", {
        headers: {
            "Authorization": `Bearer ${oauth_tokens.access_token}`,
            "Content-Type": "application/json",
        },
    });
    if (!userinfo_response.ok) throw new Error(await userinfo_response.text());

    const { email: platform_email } = z.object({ email: z.string() }).parse(await userinfo_response.json());

    return { ...oauth_tokens, platform_email };
}

We highly recommend viewing the full sample app here

Submitting Microsoft OAuth Client To Production

More documentation on the process of submitting your app for approval is available here: https://learn.microsoft.com/en-us/azure/active-directory/develop/publisher-verification-overview


Did this page help you?