【问题标题】:How do I get refresh token from microsoft graph如何从 microsoft graph 获取刷新令牌
【发布时间】:2021-10-03 23:20:01
【问题描述】:

我正在尝试创建 Outlook 电子邮件自动化工具。为了让它做一些事情,例如在给定时间为用户发送文件夹中的电子邮件。微软允许第三方api通过auth2代表用户触发微软的api。我能够检索必要的访问令牌,但我需要一个刷新令牌才能代表用户调用代码,而无需强迫他每小时登录一次。

我目前正在使用微软的 javascript 身份验证库来接收令牌。根据我的阅读,刷新令牌似乎必须在令牌请求中具有离线范围。使用下面的代码,我可以使用访问令牌进行响应,但我仍然无法获取访问令牌。

const tokenRequest = {
  scopes: [
    "https://graph.microsoft.com/Mail.ReadWrite",
    "https://graph.microsoft.com/mail.send",
    "https://graph.microsoft.com/offline_access"
  ]
};

const hostname = "http://localhost:5000";

const backendServerAdress = "http://localhost:8050";
var xhr = new XMLHttpRequest();
xhr.open("POST", backendServerAdress, true);
xhr.setRequestHeader("Content-Type", "application/json");

const msalConfig = {
  auth: {
    clientId: "something",
    redirectUri: hostname + "/homepage/index.html"
  }
};

var loginRequest = {
  scopes: ["Mail.ReadWrite", "mail.send", "offline_access"] // optional Array<string>
};

const TOKEN_ID = "token_id";
const msalInstance = new Msal.UserAgentApplication(msalConfig);

msalInstance.handleRedirectCallback((error, response) => {
  console.log("redirect callback done");
});

async function redirectToDashboard() {
  console.log("redirect to dashboard");
  // var response = await requestTokenSilent();
  var response;
  if (!response || !response.status == 200) {
    response = await requestTokenPopup();
  }

  if (response && response.status == 200) {
    xhr.send(
      JSON.stringify({
        firstname: "something",
        lastname: "something",
        accessToken: "something"
      })
    );
    location.href = hostname;
  } else {
    console.log("Unable to acquire token");
  }
}

function redirectLogin() {
  console.log("redirect called");
  if (!msalInstance.getAccount()) {
    return msalInstance
      .loginRedirect(loginRequest)
      .then(response => {
        console.log(response);
        return response;
      })
      .catch(err => {
        console.log("Authentication error: ", err);
      });
  }

  if (msalInstance.getAccount()) {
    redirectToDashboard();
  }
}

async function requestTokenSilent() {
  console.log("requestTokenSilent");
  if (msalInstance.getAccount()) {
    return msalInstance
      .acquireTokenSilent(tokenRequest)
      .then(response => {
        localStorage.setItem(TOKEN_ID, response.accessToken);
        console.log("response reached: ", response);
        resolve(response);
      })
      .catch(err => {
        if (err.name === "InteractionRequiredAuthError") {
          alert("Authentication failed try again");
        }
      });
  }
}

async function requestTokenPopup() {
  console.log("requestTokenPopup");
  if (msalInstance.getAccount()) {
    return msalInstance
      .acquireTokenPopup(tokenRequest)
      .then(response => {
        localStorage.setItem(TOKEN_ID, response.accessToken);
        return response;
      })
      .catch(err => {
        console.log(err);
        if (err.name === "InteractionRequiredAuthError") {
          alert("Authentication failed try again");
        }
      });
  }
}

【问题讨论】:

    标签: javascript oauth-2.0 outlook microsoft-graph-api msal.js


    【解决方案1】:

    MSAL.js 执行implicit flow 来获取访问令牌。此流程根本不返回刷新令牌,因为刷新令牌在隐式流程中没有用途。刷新是通过隐藏请求完成的。从上面的链接:

    隐式授权不提供刷新令牌。 id_tokens 和 access_tokens 都会在短时间内过期,因此您的应用必须准备好定期刷新这些令牌。要刷新任一类型的令牌,您可以使用 prompt=none 参数从上面执行相同的隐藏 iframe 请求,以控制身份平台的行为。如果要接收新的 id_token,请务必在 response_type 和 scope=openid 中使用 id_token,以及 nonce 参数。

    如果在您调用 requestTokenSilent 时当前访问令牌已过期,MSAL.js 会自动为您执行此操作。

    您需要一个刷新令牌,因为您的真正目标是让您的后端服务器进程访问图表。隐式不适用于此。相反,您需要使用on-behalf-of flow。您可以在那里阅读所有细节,但高级摘要是:

    • 您的 JavaScript 前端使用 MSAL.js 为后端 Web API 通过隐式流获取令牌。它在调用您的后端时在 Authorization 标头中发送此令牌。
    • 您的后端使用代表流将该令牌交换为访问令牌并刷新 Graph 令牌。

    【讨论】:

      【解决方案2】:

      我使用的是 v1 版本的 msal。 V1 版本不再支持刷新令牌。有人告诉我 msal v2 支持刷新令牌,但它目前处于测试阶段。

      【讨论】:

      • 什么版本的 MSAL? MSAL 应该为您处理刷新。基本上它不会给你刷新令牌,它会在内部管理它。
      • @JasonJohnston 我试图创建一个电子邮件自动化工具,但如果用户必须每小时登录一次,这将没有意义。我希望使用刷新令牌访问用户电子邮件 3 个月。现在,我正在考虑发布内置的前景
      • 他们不必重新登录,只要他们仍在页面上,MSAL 就会为您刷新。如果您尝试将令牌交给某些后端服务,则不应这样做。你会想在那里代表流程。
      • 出于安全原因,@JasonJohnston 访问令牌会在一小时后过期。 MSAL.js 不会为开发人员刷新令牌。它有两个功能:requestTokenSilent 和 requestTokenPopup。至少对于 msal v1,这两个函数都返回一个访问令牌(除其他外),但这些函数只有在用户在页面上时才能运行。这就是创建刷新令牌的原因,因此人们可以继续接收访问令牌,而无需用户每小时都需要进行身份验证来调用图形 API。是的,我正计划将令牌传递给后端服务,并想了解您为什么认为这是一个坏主意
      • MSAL.js 执行隐式流程,但不执行刷新令牌。这是设计使然。相反,当您在浏览器中使用用户的 cookie 调用 requestTokenSilent 时,它会在需要时为您静默刷新。这就是为什么用户必须仍然在浏览器中等。对于您想要做的事情,您应该使用on-behalf-of flow - 您的 JS 代码获得调用后端服务的授权,后端使用该令牌交换带有刷新令牌的图形令牌。
      【解决方案3】:

      首先,我不擅长JavaScript,但在我看来,您没有取出refresh_token。 Refresh_token 是长期存在的,但是当请求一个新的时,您必须在收到新的 access_token 时更新 refresh_token,因为授权服务器可能会发出一个新的。

      当我遇到这个问题时,我在这篇文章中找到了很多帮助。 Microsoft docs v2-oauth2-auth-code-flow

      【讨论】:

        【解决方案4】:

        不确定它是否有帮助,但我在寻找解决方案时偶然发现了这篇文章,我最终得到了这个有效的打字稿实现:

        export async function renewAccessToken(refreshTokenFromUser: string): Promise<string> {
          const data =
            "grant_type=refresh_token" +
            "&refresh_token=" +
            refreshTokenFromUser +
            "&client_id=" +
            config.creds.clientID +
            "&client_secret=" +
            encodeURIComponent(config.creds.clientSecret) +
            "&scope=" +
            config.creds.scope;
        
          const response = await axios({
            method: "POST",
            url: config.creds.tokenEndpoint,
            headers: { "Content-Type": "application/x-www-form-urlencoded" },
            data: data,
          });
        
          let refreshedToken;
          if (response && response.data) {
            const tokenAnswer = response.data;
            if (tokenAnswer.hasOwnProperty("access_token")) {
              refreshedToken = tokenAnswer.access_token;
            } else {
              console.warn("error in refresh token");
            }
          } else {
            console.warn("error in refresh token");
            refreshedToken = null;
          }
        
          return refreshedToken;
        }
        

        配置文件中的属性为:

        exports.creds = {
          tokenEndpoint: "https://login.microsoftonline.com/common/oauth2/token",
          // The client ID of your app in AzureActiveDirectory (required)
          clientID: "insert your ID",
          clientSecret: "insert your secret",
          scope: ["profile", "offline_access", "https://graph.microsoft.com/calendars.read", "https://graph.microsoft.com/calendars.read.shared"],
        };
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-10-26
          • 2022-01-12
          • 1970-01-01
          • 2023-01-01
          • 2021-05-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多