当用户需要同意时,如何在 Microsoft OAuth 中为 Microsoft Teams 中的应用程序获取刷新令牌?

问题描述 投票:0回答:1

我有一个适用于 Microsoft 团队的应用程序,它使用 Azure SSO 登录。

认证流程如下:

  1. 当用户在 Teams 中的频道中打开应用程序时,前端从 Microsoft Graph API 获取
    sso-token
    并将其发送到后端。
  2. 收到 soo-token 后,后端使用
    /token
    sso-token
    (包括
    scopes
    )调用 Microsoft graph API 的
    offline_access
    路由来获取
    access_token
    refresh_token
  3. 如果用户已经同意或管理员代表用户同意,则没有问题,后端同时获得
    refresh_token
    access_token
    并且后端返回
    200
    状态。 但如果需要同意,则后端会将
    403
    状态发送回前端。
  4. 收到
    403
    状态后,前端现在启动同意流程,并使用
    /authorize
    client-id
    (包括
    scopes
    )调用
    offline_access
    路由。在这种情况下,在用户同意后,我不会从 Microsoft 获得
    refresh_token
    ,而只返回
    access_token

现在,我有一个功能可以从应用程序向用户发送 Microsoft 团队活动通知,为此,我需要不时刷新访问令牌,而无需用户交互,因为发送活动通知的过程是从后端发生的。并且由于在需要用户同意的情况下后端无法获取

refresh_token
,一段时间后后端保存的
access_token
就会过期,并且所有发送活动通知的 API 调用都会导致错误.

第2步的后端代码

  1. 在后端使用
    access_token
    获取
    refresh_token
    sso-token
    的实用函数。
// other import statements
//...
const {
  MS_CLIENT_ID,
  MS_CLIENT_SECRET,
  MS_GRAPH_SCOPES,
} = require('../config/env');

const graphScopes = `https://graph.microsoft.com/User.Read https://graph.microsoft.com/${MS_GRAPH_SCOPES} offline_access`;

// receives the sso-token as parameter from front-end
const getAccessToken = async (ssoToken) => {
  try {
    const { tid } = jwt_decode(ssoToken);
    if (!tid) throw new Error('sso token is malformed');

    const accessTokenQueryParams = new URLSearchParams({
      grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
      client_id: MS_CLIENT_ID,
      client_secret: MS_CLIENT_SECRET,
      assertion: ssoToken,
      scope: graphScopes,
      requested_token_use: 'on_behalf_of',
    }).toString();

    const { data, status } = await axios({
      method: 'POST',
      url: `https://login.microsoftonline.com/${tid}/oauth2/v2.0/token`,
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      data: accessTokenQueryParams,
    });
    if (status != 200) throw new Error('could not exchange access token');

    return data;
  } catch (error) {
    console.error({
      function: 'getAccessToken',
      dir: __dirname,
      file: __filename,
      error:
        error.response && error.response.data ? error.response.data : error,
    });
    return null;
  }
};
  1. 用于 ms team 身份验证的 API 路由,使用从前端发送的
    sso-token
    来调用实用程序函数。
router.get('/auth', async (req, res) => {
  try {
    const {
      ssoToken,
      teamId,
      teamName = '',
      channelId,
      channelName = '',
    } = req.query;

    const { tid } = jwt_decode(ssoToken);
    if (!tid || !teamId)
      return res
        .status(500)
        .json({ errors: [{ msg: 'Could not exchange access token' }] });

    // CALLING THE UTILITY FUNCSTION WITH SSO TOKEN
    const data = await getAccessToken(ssoToken);
    if (!data || !data.access_token)
      res
        .status(403)
        .json({ errors: [{ msg: 'User must consent or perform MFA' }] });

    const { access_token: accessToken, refresh_token: refreshToken } = data;
    console.log({ accessToken, refreshToken });
    if (!accessToken)
      return res
        .status(500)
        .json({ errors: [{ msg: 'Could not exchange access token' }] });

    // SUCCESS
    // return 200 status
    //...
  } catch (error) {
    console.error({
      error:
        error.response && error.response.data ? error.response.data : error,
      person: req.person,
      company: req.company,
      headers: req.headers,
      params: req.params,
      url: req.originalUrl,
    });

    // this error should trigger the consent flow in the client.
    res
      .status(403)
      .json({ errors: [{ msg: 'User must consent or perform MFA' }] });
  }
});

第 4 步的前端代码,即需要同意时

  1. ConsentPopup.js
    (负责启动用户同意流程的组件)
class ConsentPopup extends React.Component {
  componentDidMount() {
    console.log("consentPopUp initialized");

    microsoftTeams.initialize();

    // Get the user context in order to extract the tenant ID
    microsoftTeams.getContext((context, error) => {
      let tenant = context["tid"]; //Tenant ID of the logged in user
      let client_id = env.REACT_APP_AZURE_APP_REGISTRATION_ID;

      let queryParams = {
        tenant: `${tenant}`,
        client_id: `${client_id}`,
        response_type: "token", //token_id in other samples is only needed if using open ID
        // offline_access is Added for RefreshToken
        scope: "https://graph.microsoft.com/User.Read https://graph.microsoft.com/TeamsActivity.Send offline_access",
        redirect_uri: window.location.origin + "/auth-end",
        nonce: crypto.randomBytes(16).toString("base64"),
      };

      let url = `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize?`;
      queryParams = new URLSearchParams(queryParams).toString();
      let authorizeEndpoint = url + queryParams;

      //Redirect to the Azure authorization endpoint. When that flow completes, the user will be directed to auth-end
      // GO TO ClosePopup.js
      console.log(authorizeEndpoint);
      window.location.assign(authorizeEndpoint);
    });
  }

  render() {
    return (
      <div>
        <h1>Please wait...</h1>
      </div>
    );
  }
}
  1. ClosePopup.js
    (负责完成用户同意流程的组件)
class ClosePopup extends React.Component {

    componentDidMount(){
      microsoftTeams.initialize();

      //The Azure implicit grant flow injects the result into the window.location.hash object. Parse it to find the results.
      let hashParams = this.getHashParameters();
      console.log({hashParams});
      //If consent has been successfully granted, the Graph ACCESS TOKEN and REFRESH TOKEN should be present as a field in the dictionary.
      if (hashParams["access_token"]){
        //Notifify the showConsentDialogue function in Tab.js that authorization succeeded. The success callback should fire. 
        //SENDING BOTH REFRESH TOKEN AND ACCESS TOKEN
        microsoftTeams.authentication.notifySuccess(hashParams); 
      } else {
        microsoftTeams.authentication.notifyFailure("Consent failed");
      }
    }

    getHashParameters() {
      let hashParams = {};
      console.log({hash: window.location.hash, hashSubStr: window.location.hash.substr(1)});
      window.location.hash.substr(1).split("&").forEach(function(item) {
        let [key,value] = item.split('=');
        hashParams[key] = decodeURIComponent(value);
      });
      console.log('Get Hash Params');
      console.log({hashParams});
      return hashParams;
  }    

    render() {
      return (
        <div>
          <h1>Consent flow complete.</h1>
        </div>
      );
    }
}

export default ClosePopup;
reactjs oauth-2.0 microsoft-graph-api microsoft-teams refresh-token
1个回答
0
投票

拨款可能不会立即完成。根据我的经验,最长可达 30 秒。 IE。授权调用返回,但后端仍然无法“工作”。

在第二种情况下,您可以尝试继续查询,直到获得refresh_token。 IE。只需执行多次(在后端)。 IE。尝试多次交换 sso 令牌。我想几秒钟后,refresh_token 就会开始出现。

© www.soinside.com 2019 - 2024. All rights reserved.