Microsoft Graph getSchedule 方法导致 InvalidAuthenticationToken 错误代码 80049217

Microsoft Graph getSchedule method results in InvalidAuthenticationToken error code 80049217

问题:

我正在创建一个 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"
    }
  }
}

我不知道到底是什么问题。我希望有人能够帮助我。我确实想道歉,因为这是我在 Whosebug 上的第一个 post,我不知道这是否是一个好的 post 以及是否有足够的信息。

我试过的:

在我开始创建 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 上的 link 授权:

<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。状态是用户电子邮件,因为稍后在环回 api 中需要它。

因此,我使用具有管理员权限的 Microsoft 帐户登录,并接受了要求使用的权限。完成后,它重定向到我构建的 api。

这个api完全是使用Loopback 3构建的,它使用MongoDB作为数据库。因此,要使用环回,我必须制作 2 个模型,1 个用于网站上的登录用户 (AuthUser),1 个用于来自 Microsoft Graph (MSGraphAuth) 的访问令牌。其中 AuthUser 模型与 MSGraphAuth 模型有 "hasOne relation"。

除了使用过的模型之外,我还在 /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");
                });
            });
        });
    }
}

所以在这里我首先根据州的电子邮件地址找到用户,这样我就可以将令牌添加到该用户,但首先我检查查找和 admin_consent 中的问题。当没有问题时,我检索带有客户端 ID、秘密、默认范围和 grant_type 的令牌,如 documentation 中所述的 client_credentials。当我收到没有问题的令牌时,我通过访问指定用户的相关模型 post 它们。 (通常,如果这样做了并且没有问题,我会用布尔值更新用户,这样它就知道它已经过身份验证而无需访问相关模型,该模型有效但由于某种原因没有在数据库中更新。)

现在我被重定向到该网站,我只需要注销即可刷新 cookie,这样它就知道我已通过 Microsoft Graph 进行身份验证。所以当我去查看我选择的资源邮箱的预订时,我会看到这些信息,但这不起作用。

当我尝试检索预订信息时,我通过我的 /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);
            })
        });
    }
}

在这里,我首先搜索在路由中指定的用户,并将具有访问令牌的相关模型添加到结果用户。因此,根据搜索中的所有信息,我可以检索由于某种原因无效的预订信息。

所以如果有人知道问题可能是什么,请让我知道,或者如果有人对我正在采用的方法有任何建议,我们随时欢迎他们。

提前致谢!

所以我找到了我想要实现的解决方案,但我不得不使用 Microsoft 库。所以我找到了 Microsoft Graph demo app 并编辑了我想要达到的目标。当它工作时,我只是将它复制到我的项目中并进一步编辑它,以便它只使用我需要的东西。