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# 中发送电子邮件?
代码工作流程如下:
- 在浏览器上,我调用 msalInstance.loginPopup(loginRequest) 方法在用户选择他们的 outlook 帐户后获取 accesstoken。
- 然后我将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; }
}
我正在使用 C# Microsoft Graph 发送电子邮件,但是当我调用 await graphClient.Me.SendMail(message).Request 时遇到错误“对象引用未设置到对象的实例” ().PostAsync() 方法。 我尝试先调用 var request = graphClient.Me.SendMail(message).Request() 并看到请求对象不为空但是当我调用 request.PostAsync() 时它给出了错误,所以我认为 PostAsync( ) 方法有问题或者我遗漏了什么。
我不确定我们是否可以使用来自 msal-browser 的访问令牌在 C# 中发送电子邮件?
代码工作流程如下:
- 在浏览器上,我调用 msalInstance.loginPopup(loginRequest) 方法在用户选择他们的 outlook 帐户后获取 accesstoken。
- 然后我将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; }
}