【发布时间】:2020-02-27 18:00:13
【问题描述】:
问题:
我正在创建一个 Angular 网络应用程序,公司可以在其中注册 1 个用户。使用此帐户,他们可以稍后添加其 Microsoft 资源帐户,但除此之外,在他们使用其 Microsoft 帐户授权 Microsoft Graph 之前,他们无法执行任何操作。当他们被授权(并且他们的访问令牌被保存)时,他们可以检索他们选择的资源帐户的预订信息。此信息将显示在整个公司的不同屏幕上,以便他们知道在特定时刻哪些会议室已被占用或预订。
到目前为止,我已经能够完成授权过程,但是在尝试检索我收到的预订时:
"error": {
"code": "InvalidAuthenticationToken",
"message": "CompactToken parsing failed with error code: 80049217",
"innerError": {
"request-id": "1c24ceea-fa85-4cd0-bb0d-ab14ae26b65b",
"date": "2020-02-19T15:54:07"
}
}
}
我不知道到底是什么问题。我希望有人能够帮助我。我想道歉,因为这是我在 StackOverflow 上的第一篇文章,我不知道这是否是一篇好文章以及是否有足够的信息。
我尝试过的:
在我开始创建 web 应用程序和 api 之前,我一直在尝试使用图形浏览器,在那里我能够进行日历请求,并且响应看起来很有希望,所以我开始着手这个项目。
一开始我使用的授权过程是Microsoft authorization process on behalf of the signed-in user。这样做的原因是因为用户是授权和请求预订信息的人。当我要开始检索预订信息时,我意识到我们必须从不同的资源邮箱中检索这些信息。因此,由于普通用户无法访问其组织的其他用户,因此我们需要一个管理员用户。
那时我发现了我们现在正在使用的授权过程,即Microsoft authorization process without signed-in user。这需要管理员同意,并且能够从多个资源邮箱中检索预订信息。
现在,当授权过程完全运行时,我想尝试获取预订信息,但每次我得到 InvalidAuthenticationToken(完整错误见上文)。
我检查了 azure 门户中的权限并进行了更改,希望能解决问题,但显然没有。所以现在这些权限看起来像这样:Permissions in the azure portal (image)
所以我开始研究我所做的授权过程。 第一个用户通过 Angular webapp 上的链接进行授权:
<a
href="https://login.microsoftonline.com/common/adminconsent
?client_id=**CLIENT_ID**
&state={{user.email}}
&redirect_uri=**REDIRECT_URL_TO_API**"
type="button"
class="btn btn-icon btn-danger"
aria-label="error"
>
<clr-icon shape="error-standard"></clr-icon>
Authorize
</a>
我检查了正确的客户端 ID,与 url 相同,因为它可以毫无问题地进入 api。 state 是用户的 email,因为后面的 loopback api 需要这个。
所以我使用具有管理员权限的 microsoft 帐户登录,并且我接受了要求使用的权限。完成后,它重定向到我构建的 api。
这个api完全使用Loopback 3构建,它使用MongoDB作为数据库。因此,要使用环回,我必须制作 2 个模型,1 个用于网站上的登录用户 (AuthUser),1 个用于来自 Microsoft Graph (MSGraphAuth) 的访问令牌。其中 AuthUser 模型与 MSGraphAuth 模型具有“hasOne 关系”。
除了使用的模型之外,我还在 /server/boot 目录中设置了中间件。在那里我编辑了 root.js 文件,所以我可以添加一些快速路由。 root.js 文件如下所示:
'use strict';
const express = require('express');
const erouter = require('express').Router();
var cors = require('cors');
const msauth = require('./msAuth.service');
const getbookings = require('./retrieveBookings.service');
module.exports = function(server) {
// Install a `/` route that returns server status
var router = server.loopback.Router();
router.get('/', server.loopback.status());
var app = express();
app.use(cors({
origin: "*"
}));
erouter.get('/API/auth', msauth());
erouter.get('/API/bookings/:userid', getbookings());
app.use(erouter);
server.use(app);
server.use(router);
};
所以当微软重定向时,它会转到/API/auth 路由,后面的代码如下所示:
'use strict';
const request = require('request');
var querystring = require('querystring');
const server = require("../server");
module.exports = function() {
return function(req, res, next) {
var AuthUser = server.models.AuthUser;
var user = {};
AuthUser.find({ where: { email: req.query.state } }, function(err, au) {
if (err) return next(err);
if (au.length != 1) return res.redirect("https://*WEBSITE*/settings?error=user_not_found");
user = au[0];
if (!req.query.admin_consent) return res.redirect("https://*WEBSITE*/settings?error=permission_denied");
const dataTokenReq = querystring.stringify({
"client_id": process.env.client_id,
"scope": "https://graph.microsoft.com/.default",
"client_secret": process.env.client_secret,
"grant_type": "client_credentials"
});
const postOptions = {
url: `https://login.microsoftonline.com/${req.query.tenant}/oauth2/v2.0/token`,
method: 'POST',
body: dataTokenReq,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Host': 'login.microsoftonline.com'
}
}
request(postOptions, function(err, resp, body) {
if (err) return next(err);
var body = JSON.parse(body);
const dataMsAuth = querystring.stringify({
"created": new Date().toDateString(),
"userId": user.id,
"token_type": body.token_type,
"expires": body.expires_in,
"access_token": body.access_token,
"tenant": req.query.tenant
});
const postMSAuth = {
url: "https://*API_SITE*/api/AuthUsers/" + user.id + "/msauth",
method: 'POST',
body: dataMsAuth,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
request(postMSAuth, function(err, resp, body) {
if (err) return next(err);
// Update user
console.log(body);
res.redirect("https://*WEBSITE*/settings?auth=success");
});
});
});
}
}
所以在这里我首先根据该州的电子邮件地址找到用户,以便我可以将令牌添加到该用户,但首先我检查 find 和 admin_consent 中的问题。当没有问题时,我使用客户端 ID、秘密、默认范围和 grant_type 检索令牌作为 client_credentials,如 documentation 中所述。当我收到没有问题的令牌时,我通过访问指定用户的相关模型来发布它们。 (通常,如果这样做并且没有问题,我会用布尔值更新用户,这样它就知道它已通过身份验证而无需访问相关模型,该模型有效但由于某种原因没有在数据库中更新。)
现在我被重定向到该网站,我只需注销即可刷新 cookie,因此它知道我已通过 Microsoft Graph 进行身份验证。所以当我去检查我选择的资源邮箱的预订时,我会看到这个信息,但这不起作用。
当我尝试检索预订信息时,我会通过我的 Loopback 环境中的自定义路由(/API/bookings/:userid)提出此问题。在这条路线上,我有以下代码来检索预订信息:
'use strict';
const request = require('request');
var querystring = require('querystring');
const server = require("../server");
module.exports = function() {
return function(req, res, next) {
/**
* retrieve bookings user
*/
var AuthUser = server.models.AuthUser;
AuthUser.findById(req.params.userid, { include: 'msauth' }, function(err, au) {
if (err) return next(err);
if (au == null) return next;
console.log(au);
if (au.msauth == null) return next;
const data = querystring.stringify({
"schedules": ["meetingroom@example.com", *RESOURCE_MAILBOX*],
"startTime": {
"dateTime": "2020-02-20T01:00:00",
"timeZone": "Europe/Paris"
},
"endTime": {
"dateTime": "2020-02-29T23:00:00",
"timeZone": "Europe/Paris"
}
});
const postOptions = {
url: "https://graph.microsoft.com/v1.0/me/calendar/getSchedule",
method: 'POST',
body: data,
headers: {
'Authorization': "Bearer " + au.msauth.access_token,
'Content-Type': 'application/json',
'Prefer': "outlook.timezone='Pacific Standard Time'"
}
}
request(postOptions, function(err, resp, body) {
if (err) return next(err);
console.log(body);
res.json(body);
})
});
}
}
这里我首先搜索路由中指定的用户,并将带有访问令牌的相关模型添加到结果用户。因此,使用搜索中的所有信息,我可以检索由于某种原因不起作用的预订信息。
因此,如果有人知道问题可能是什么,请告诉我,或者如果有人对我正在采取的方法有建议,我们随时欢迎。
提前致谢!
【问题讨论】:
标签: node.js angular express microsoft-graph-api loopback