C# Microsoft Graph - 如何从 msal-browser 发送带有访问令牌的电子邮件

C# Microsoft Graph - How to send email with access token from msal-browser

我正在使用 C# Microsoft Graph 发送电子邮件,但是当我调用 await graphClient.Me.SendMail(message).Request 时遇到错误“对象引用未设置到对象的实例” ().PostAsync() 方法。 我尝试先调用 var request = graphClient.Me.SendMail(message).Request() 并看到请求对象不为空但是当我调用 request.PostAsync() 时它给出了错误,所以我认为 PostAsync( ) 方法有问题或者我遗漏了什么。

我不确定我们是否可以使用来自 msal-browser 的访问令牌在 C# 中发送电子邮件?

代码工作流程如下:

  1. 在浏览器上,我调用 msalInstance.loginPopup(loginRequest) 方法在用户选择他们的 outlook 帐户后获取 accesstoken。
  2. 然后我将accesstoken从客户端发送到后端发送邮件。但是我在这一步遇到了错误 await graphClient.Me.SendMail(message).Request().PostAsync();

注意:我想从后端 (C#) 发送电子邮件而不是 javascript 因为我想在后端处理一些逻辑代码。

这是SDK: https://alcdn.msauth.net/browser/2.7.0/js/msal-browser.min.js 和 Microsoft.Graph 版本 1.21.0

这里是javascript代码:

var msalConfig = {
    auth: {
        clientId: "clientId",
        authority: "https://login.microsoftonline.com/{tenantId}",
        redirectUri: location.origin
    }
};
    
var msalInstance = new msal.PublicClientApplication(msalConfig);
    
var loginRequest = {
    scopes: ["User.Read", "Mail.Send"]
};
msalInstance.loginPopup(loginRequest)
    .then((res) => {
        methodCallAPIFromDotNet(res.accessToken); // This is method is used to call API from C# with the accessToken
    })
    .catch(error => {
        console.error(error);
    });

这是 C# 代码:

public async Task<bool> Send(string accessToken)
        {
            var message = new Message
            {
                Subject = "Meet for lunch?",
                Body = new ItemBody
                {
                    ContentType = BodyType.Text,
                    Content = "The new cafeteria is open."
                },
                ToRecipients = new List<Recipient>
                {
                    new Recipient
                    {
                        EmailAddress = new EmailAddress
                        {
                            Address = "to email address"
                        }
                    }
                },
                Attachments = new MessageAttachmentsCollectionPage()
            };

            var authProvider = new DelegateAuthenticationProvider(requestMessage =>
            {
                requestMessage.Headers.Authorization =
                    new AuthenticationHeaderValue("Bearer", accessToken);
                return System.Threading.Tasks.Task.FromResult(0);
            });

            var graphClient = new GraphServiceClient(authProvider);

            var me = await graphClient.Me.Request().GetAsync(); // I can get user info here.

            await graphClient.Me.SendMail(message).Request().PostAsync(); // Error here
            return true;
        }

这里有例外:

at Microsoft.Graph.HttpProvider.d__18.MoveNext()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.Graph.BaseRequest.d__35.MoveNext()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.Graph.BaseRequest.d__30.MoveNext()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()

OP 的分辨率:

used login.microsoftonline.com/consumers instead of login.microsoftonline.com/ for the authority attribute so now I can read messages or send email.

那么它仍然可以使用下面的代码发送邮件

var graphClient = new GraphServiceClient(authProvider);
await graphClient.Me.SendMail(message).Request().PostAsync();

============================================= =============

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script src="js/msal.js"></script>
        <script src="js/jquery-3.5.1.min.js" type="text/javascript" charset="utf-8"></script>
        <!-- <script type="text/javascript" src="https://js.monitor.azure.com/scripts/b/ai.2.min.js"></script> -->
    </head>
    <body>
        <div style="margin-top: 15px; background-color: #DDDDDD;">
            <button type="button" id="signIn" onclick="signIn()">Sign In</button>
            <button type="button" id="getAccessToken" onclick="getAzureAccessToken()">getAccessToken</button>
            <h5 class="card-title" id="welcomeMessage">Please sign-in to see your profile and read your mails</h5>
            <div>
                <div>
                    accesstoken :
                    <div id="accesstoken">
                        
                    </div>
                </div>
            </div>
        </div>
            <script type="text/javascript">
            
                const msalConfig = {
                    //clientsecret:"client_secret"
                    auth: {
                        clientId: "your_azuread_app_client_id",
                        authority: "https://login.microsoftonline.com/your_tenant_name.onmicrosoft.com",
                        redirectUri: "your_home_page_url_like_http://localhost:8848/new_file.html",
                    },
                    cache: {
                        cacheLocation: "sessionStorage", // This configures where your cache will be stored
                        storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
                    }
                };
        
                //scope used for sign in
                const loginRequest = {
                    scopes: ["openid", "profile", "User.Read"]
                };
                //scope used for generating access token
                const AzureMgmtScops ={
                    scopes:["User.Read", ...]
                }
                
                let accessToken = '';
                const myMSALObj = new Msal.UserAgentApplication(msalConfig);
        
                function signIn() {
                    myMSALObj.loginPopup(loginRequest)
                        .then(loginResponse => {
                            console.log("id_token acquired at: " + new Date().toString());
                            console.log(loginResponse);
        
                            if (myMSALObj.getAccount()) {
                                showWelcomeMessage(myMSALObj.getAccount());
                                getAzureAccessToken();                              
                            }
                        }).catch(error => {
                            console.log(error);
                        });
                }
        
                function showWelcomeMessage(account) {
                    document.getElementById("welcomeMessage").innerHTML = `Welcome ${account.name}`;
                }
        
                function getAzureAccessToken(){
                    myMSALObj.acquireTokenSilent(AzureMgmtScops).then(tokenResponse => {
                        showAccesstoken(tokenResponse.accessToken)
                        console.info("======the accesstoken is ======:"+tokenResponse.accessToken);
                    }).catch(function (error) {
                         console.log(error);
                    })
                }
                
                function showAccesstoken(data){
                    document.getElementById("accesstoken").innerHTML = JSON.stringify(data, null, 2);
                }
            </script>
    </body>
</html>

============================更新================ ================

您使用 var graphClient = new GraphServiceClient(authProvider); 初始化了 graphClient,并且访问令牌是从前端传递的,所以我认为您按照 this sample 在这里使用了 on-behalf-flow。但是您提供的代码片段在这里有所不同。所以我想这就是发送邮件失败的原因。

这里你有2个选项,一个是阅读on-behalf-flow,如果你同意使用这里的流程,你只需要按照我上面提供的教程完成代码并尝试。

不过我想你可能更喜欢第二种方式。在我看来,你需要你的后端做的是执行一些逻辑来完成电子邮件,然后你调用 api 来发送电子邮件。如果是这样,为什么不直接发送 http 请求,因为您已经获得了访问令牌。

您可以检查访问令牌是否可用于发送电子邮件,只需尝试在邮递员等工具中使用令牌调用 api。如果您收到 202 响应,则表示令牌正常。

然后你需要像这样修改你的发送电子邮件方法,它对我有用:

    public async Task sendAsync() {
                var mesg = new Message
                {
                    Subject = "Meet for lunch?",
                    Body = new ItemBody
                    {
                        ContentType = BodyType.Text,
                        Content = "The new cafeteria is open."
                    },
                    ToRecipients = new List<Recipient>
                    {
                        new Recipient
                        {
                            EmailAddress = new EmailAddress
                            {
                                Address = "tiny-wa@outlook.com"
                            }
                        }
                    },
                    Attachments = new MessageAttachmentsCollectionPage()
                };
                var temp = new MailContentModel
                {
                    message = mesg
                };
                var jsonStr = JsonSerializer.Serialize(temp);
                string token = "your_token";
                var httpClient = new HttpClient();
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
                HttpResponseMessage response = await httpClient.PostAsync("https://graph.microsoft.com/v1.0/me/sendMail", new StringContent(jsonStr, Encoding.UTF8, "application/json"));
            }

using Microsoft.Graph;

public class MailContentModel
    {
        public Message message { get; set; }
    }