skip to Main Content

I am trying to book an event in Outlook calendar through Graph API. I have created the application and granted the permission as shown in the screenshot.
The application has both Delegated permission and Application permission
Application Permission

Here is the code that I am using.

import axios from 'axios';
import querystring from 'querystring';

const tenantId = '123456'; // Your Directory (Tenant) ID
const clientId = '-558d64288141'; // Your Application (Client) ID
const clientSecret = 'ZXc8Q~t'; // Your Application Secret Value

async function getAccessToken() {
    const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
    const params = {
        grant_type: 'client_credentials',
        client_id: clientId,
        client_secret: clientSecret,
        scope: 'https://graph.microsoft.com/.default'
    };

    const response = await axios.post(tokenEndpoint, querystring.stringify(params), {
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        }
    });

    return response.data.access_token;
}

async function bookCalendarEvent(accessToken, eventDetails) {
    const endpoint = 'https://graph.microsoft.com/v1.0/users/[email protected]/events'; // Updated endpoint
    
    const response = await axios.post(endpoint, eventDetails, {
        headers: {
            Authorization: `Bearer ${accessToken}`,
            'Content-Type': 'application/json'
        }
    });

    return response.data;
}

export const handler = async (event) => {
    try {
        // Get access token using client credentials
        const accessToken = await getAccessToken();

        // Sample event details
        const eventDetails = {
            subject: 'Meeting with Lambda',
            body: {
                contentType: 'HTML',
                content: 'This is a meeting scheduled by AWS Lambda'
            },
            start: {
                dateTime: '2024-08-27T09:00:00', // Sample start date and time
                timeZone: 'UTC'
            },
            end: {
                dateTime: '2024-08-27T10:00:00', // Sample end date and time
                timeZone: 'UTC'
            },
            attendees: [
                {
                    emailAddress: {
                        address: '[email protected]', // Sample attendee email
                        name: 'John Doe'
                    },
                    type: 'required'
                }
            ]
        };

        // Book the calendar event
        const result = await bookCalendarEvent(accessToken, eventDetails);

        return {
            statusCode: 200,
            body: JSON.stringify({
                message: 'Event booked successfully',
                eventId: result.id
            })
        };
    } catch (error) {
        console.error('Error booking calendar event:', error);
        return {
            statusCode: 500,
            body: JSON.stringify({
                message: 'Error booking event',
                error: error.message
            })
        };
    }
};

But when I run the AWS Lambda I get this error

{
  "statusCode": 500,
  "body": "{"message":"Error booking event","error":"Request failed with status code 403"}"
}

So I am not sure what I am doing wrong
Any help
thanks

2

Answers


  1. Since you are using client credentials flow, only application permissions will work. Delegated permissions only work if a user is involved in the token acquisition; which they are not in this case.

    For the application permission to work, an app role assignment needs to be granted to the service principal of your app. Pressing the Grant admin consent button will do this (and also add the necessary objects for all the other required permissions you have). You (or someone else) needs to have Application Admin / Cloud Application Admin / Global Admin role.

    Login or Signup to reply.
  2. I agree with @juunas, you need to switch to delegated flows like authorization code flow to have access to only signed-in user’s calendar.

    I registered one Azure AD application and granted Calendars.ReadWrite permission of Delegated type as below:

    enter image description here

    Now, I added redirect URI as http://localhost:3000/callback in Web platform of application like this:

    enter image description here

    In my case, I used below modified code that works with authorization code flow to acquire token and books event in signed-in user calendar:

    index.js:

    const express = require('express');
    const axios = require('axios');
    const querystring = require('querystring');
    
    const app = express();
    const port = 3000;
    
    let storedAccessToken = '';
    
    const tenantId = 'tenantID';
    const clientId = 'appID';
    const clientSecret = 'secret';
    const redirectUri = 'http://localhost:3000/callback';
    
    app.get('/login', async (req, res) => {
        const authorizationUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize?` + 
        querystring.stringify({
            client_id: clientId,
            response_type: 'code',
            redirect_uri: redirectUri,
            response_mode: 'query',
            scope: 'openid profile offline_access Calendars.ReadWrite',
            state: '12345'
        });
    
        const { default: open } = await import('open');
        await open(authorizationUrl);
    
        res.send('Opening Microsoft login page...');
    });
    
    app.get('/callback', async (req, res) => {
        const code = req.query.code;
    
        async function getAccessToken(code) {
            const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
    
            const response = await axios.post(tokenEndpoint, querystring.stringify({
                grant_type: 'authorization_code',
                client_id: clientId,
                client_secret: clientSecret,
                code: code,
                redirect_uri: redirectUri,
                scope: 'openid profile offline_access Calendars.ReadWrite'
            }), {
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
            });
    
            return response.data.access_token;
        }
    
        try {
            const accessToken = await getAccessToken(code);
            console.log('Access Token:', accessToken);
            storedAccessToken = accessToken;
            res.send('Login successful! You can now book an event.');
        } catch (error) {
            console.error('Error obtaining access token:', error.response ? error.response.data : error.message, error.stack);
            res.status(500).send('Error obtaining access token: ' + error.message);
        }
    });
    
    app.get('/book-event', async (req, res) => {
        try {
            if (!storedAccessToken) {
                return res.status(401).json({ message: 'User is not authenticated. Please login first.' });
            }
    
            console.log('Using Stored Access Token:', storedAccessToken);
    
            const eventDetails = {
                subject: 'Meeting with Lambda',
                body: {
                    contentType: 'HTML',
                    content: 'This is a meeting scheduled by AWS Lambda'
                },
                start: {
                    dateTime: '2024-08-27T09:00:00',
                    timeZone: 'UTC'
                },
                end: {
                    dateTime: '2024-08-27T10:00:00',
                    timeZone: 'UTC'
                },
                attendees: [
                    {
                        emailAddress: {
                            address: '[email protected]',
                            name: 'Sridevi'
                        },
                        type: 'required'
                    }
                ]
            };
    
            const calendarEndpoint = `https://graph.microsoft.com/v1.0/me/events`; 
    
            const response = await axios.post(calendarEndpoint, eventDetails, {
                headers: {
                    Authorization: `Bearer ${storedAccessToken}`,
                    'Content-Type': 'application/json'
                }
            });
    
            res.json({ message: 'Event booked successfully', eventId: response.data.id });
        } catch (error) {
            console.error('Error booking event:', error.response ? error.response.data : error.message, error.stack);
            res.status(500).json({ message: 'Error booking event', error: error.message });
        }
    });
    
    app.listen(port, () => {
        console.log(`Server running on http://localhost:${port}`);
    });
    

    Now, run the code and visit http://localhost:3000/login in browser that asks user to login and gives code in address bar after completing authentication like this:

    enter image description here

    When I ran http://localhost:3000/book-event in browser, it booked the event in signed-in user’s calendar successfully like this:

    enter image description here

    If you try to book event in different user’s calendar, it will give 403 error as access will be denied for users other than signed-in user like this:

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search