What are the necessary permission to send an email on behalf of a user

Lucian Simo 0 Reputation points
2023-04-21T15:00:05.2966667+00:00

For a few days now I'm struggling to implement a feature where the user can authenticate using their outlook email and schedule an email to be send. Therefore, the API should be able to send an email on behalf of the authenticated user.

I've used the following code to authenticate the user and send an email:

import * as msal from "@azure/msal-node";
import { Client } from "@microsoft/microsoft-graph-client";

export const msalConfig: msal.Configuration = {
  auth: {
    clientId: “CLIENT_ID”,
    authority: "https://login.microsoftonline.com/TENENT_ID”,
    clientSecret: “CLIENT_SECRET”,
  },
};

export const cca = new msal.ConfidentialClientApplication(msalConfig);

export const getGraphClient = (accessToken: string | null) => {
  const graphClient = Client.init({
    authProvider: (done) => {
      done(null, accessToken);
    },
  });
  return graphClient;
};

export const protectedResources = {
  graphMe: {
    meEndpoint: "https://graph.microsoft.com/v1.0/me",
    usersEndpoint: "https://graph.microsoft.com/v1.0/users/",
    scopes: [
      "openid",
      "profile",
      "user.read",
      "mail.send",
      "offline_access",
      //  "Mail.Send",
      //  "https://graph.microsoft.com/.default",
      //  "https://graph.microsoft.com/Mail.Send.Shared",
      //  "https://graph.microsoft.com/Mail.Read",
      //  "https://graph.microsoft.com/profile",
      //  "https://graph.microsoft.com/.default",
      //  "https://outlook.office.com/IMAP.AccessAsUser.All",
    ],
  },
};
// Code to start the authentication flow
const state = request.query.state as string;

try {
    // Get the authorization URL
    const authCodeUrlParameters: msal.AuthorizationUrlRequest = {
    	scopes: protectedResources.graphMe.scopes,
      	redirectUri: "https://example.com/outlookOauth2Callback",
    	codeChallengeMethod: "S256",
      	state: state,
    };
    const authorizeUrl = await cca.getAuthCodeUrl(authCodeUrlParameters);
    response.redirect(authorizeUrl);
    return;
} catch (error) {
    console.log(error);
    response.status(500).send(error);
    return;
}
// Code to get the access Token
const code = request.query.code as string;
const state = request.query.state as string;

try {
	const tokenRequest: msal.AuthorizationCodeRequest = {
		code: code,
		scopes: protectedResources.graphMe.scopes,
		redirectUri:
		"https://example.com/outlookOauth2Callback",
		codeVerifier: request.query.code_verifier as string,
	};

	const accessToken = await cca.acquireTokenByCode(tokenRequest);
	await saveUserToken(code, accessToken);
	console.log("Successfully authorized");

	response.redirect("https://example.com/callback.html?code=" + code + "&state=" + state);
	 return;
} catch (error) {
	console.log(error);
	response.status(500).send(error);
	return;
}
// Code to send the email
const graphUser = await getGraphClient(token.accessToken)
      .api(protectedResources.graphMe.meEndpoint)
      .get();

functions.logger.log("Graph User: " + JSON.stringify(graphUser));

const message = {
      subject: subject,
      body: {
        contentType: "HTML",
        content: body,
      },
      toRecipients: [
        {
          emailAddress: {
            toName: toName,
            address: toEmail,
          },
        },
      ],
      from: { emailAddress: { fromName: fromName, address: graphUser.email } },
};

const response = await getGraphClient(token[1].accessToken)
   .api(protectedResources.graphMe.usersEndpoint + `${graphUser.id}/sendMail`)
   .post({ message });

functions.logger.log("Response: " + JSON.stringify(response));

The authentication flow works correctly and I'm able to save the access token. Also the Graph me call works as expected. I get the expected response back with the users data.

The problem is when I'm trying to send the email. I get the following error: {"statusCode":401,"code":null,"requestId":null,"date":"2023-04-21T14:18:46.748Z","body":{"_writeState":{"0":0,"1":0},"_readableState":{"objectMode":false,"highWaterMark":16384,"buffer":{"head":null,"tail":null,"length":0},"length":0,"pipes":null,"pipesCount":0,"flowing":null,"ended":false,"endEmitted":false,"reading":false,"sync":false,"needReadable":false,"emittedReadable":false,"readableListening":false,"resumeScheduled":false,"emitClose":true,"autoDestroy":false,"destroyed":false,"defaultEncoding":"utf8","awaitDrainWriters":null,"multiAwaitDrain":false,"readingMore":false,"decoder":null,"encoding":null},"readable":true,"_events":{"error":[null,null]},"_eventsCount":6,"_writableState":{"objectMode":false,"highWaterMark":16384,"finalCalled":false,"needDrain":false,"ending":false,"ended":false,"finished":false,"destroyed":false,"decodeStrings":true,"defaultEncoding":"utf8","length":10,"writing":true,"corked":0,"sync":false,"bufferProcessing":false,"writelen":10,"afterWriteTickInfo":null,"bufferedRequest":null,"lastBufferedRequest":null,"pendingcb":1,"prefinished":false,"errorEmitted":false,"emitClose":true,"autoDestroy":false,"bufferedRequestCount":0,"corkedRequestsFree":{"next":null,"entry":null}},"writable":true,"allowHalfOpen":true,"_transformState":{"needTransform":false,"transforming":true,"writechunk":{"type":"Buffer","data":[31,139,8,0,0,0,0,0,4,10]},"writeencoding":"buffer"},"_hadError":false,"bytesWritten":0,"_handle":{"buffer":{"type":"Buffer","data":[31,139,8,0,0,0,0,0,4,10]},"availOutBefore":16384,"availInBefore":10,"inOff":0,"flushFlag":2},"_outBuffer":{"type":"Buffer","data":[85,85,85,85,85,85,80,0,0,0,0,0,0,0]},"_outOffset":0,"_chunkSize":16384,"_defaultFlushFlag":2,"_finishFlushFlag":2,"_defaultFullFlushFlag":3,"_maxOutputLength":2147483647,"_level":-1,"_strategy":0}} I think I'm not using the correct permission, but I've tried many (All the commented out permission as well) of them and still getting the same error message. I'm running out of options.

Do you guys know what might go wrong here?

Thanks in advance

Microsoft Graph
Microsoft Graph
A Microsoft programmability model that exposes REST APIs and client libraries to access data on Microsoft 365 services.
10,858 questions
0 comments No comments
{count} votes

4 answers

Sort by: Most helpful
  1. Konstantinos Passadis 17,376 Reputation points MVP
    2023-04-21T15:05:25.65+00:00

    Hello @Lucian Simo Did you added the required Permissions via Azure AD App Registratons ? Have a look at the image In case ths helped kindly mark the Answer as Accepted! User's image


  2. Konstantinos Passadis 17,376 Reputation points MVP
    2023-04-24T12:58:22.48+00:00

    Hello @Lucian Simo ! maybe i found something There is a small issue in the code to send the email, the line const response = await getGraphClient(token[1].accessToken) should be const response = await getGraphClient(token.accessToken) So can you try it ? Is it there in purpose ? BR!


  3. Konstantinos Passadis 17,376 Reputation points MVP
    2023-04-24T13:34:52.9966667+00:00

    Hello @Lucian Simo ! Can you try to add also API Permissions to the Principal of Exchnage Online ? User's image

    I believe this may solve your issue ! please send an update as soon as you can and kindy mark the answer as Accepted in case it helped! BR


  4. Konstantinos Passadis 17,376 Reputation points MVP
    2023-04-24T14:02:13.0633333+00:00

    Hi @Lucian Simo In regards to the previous comment Select the Principal ( App Registration ) , API Perissions , Add permissions ( The Plus Sign ) There you can find "APIs my organization uses" From the Search Box start typing Office , you will get the Office Exchange Online API Set the required permissions as you did earlier from the MS Graph Try to run the code-function-application and post the Logs from Azure Portal , as well as the Application Insights logs if you have activated In case this resolved your issue kindly mark the answer as Accepted , otherwise keep us informed so we can assist you BR