0

I have an Outlook add-ins using React + TypeScript, debugging locally with Node.js. I'm using the office-js-helpers library to authenticate and the msgraph-sdk-javascript Graph client library. As a POC I'm simply trying to verify that I can successfully call Graph, by retrieving details of the current email by its id. I can successfully use the office-js-helpers Authenticator to authorize the app, and successfully retrieve a token.

However, when I use the Graph client to make a call to v1/me/messages/{ID}, I get:

"401 InvalidAuthenticationToken: Access token validation failure"

I'm not sure whether this is a problem with the way I'm using the Authenticator, or a problem with my add-in or app's manifests. My add-in is using these for AppDomains:

<AppDomains>
  <AppDomain>https://localhost:3000</AppDomain>
  <AppDomain>https://login.microsoftonline.com</AppDomain>
</AppDomains>

I am using https://localhost:3000 as my app's redirect URI, with implicit auth enabled.

If I use the force option with the authenticate method, I also get this error:

Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://login.microsoftonline.com') does not match the recipient window's origin ('https://localhost:3000').

However, I am able to retrieve a token.

What am I doing wrong? I'm not certain about the flow for the Authenticator, in terms of when to use authenticator.tokens.get and authenticator.authenticate. For the first run I assume always authenticate and no need to use tokens.get, and for second run I assume just use tokens.get, but if I try either of those or always both it doesn't seem to change the result of an invalid token.

import * as React from "react";
import { Button, ButtonType, TextField } from "office-ui-fabric-react";
import { Authenticator, Utilities, DefaultEndpoints } from "@microsoft/office-js-helpers";
import * as Graph from "@microsoft/microsoft-graph-client";

export default class GetItemOJSHelpers extends React.Component<any, any> {
  constructor(props) {
    super(props);
    this.getEmail = this.getEmail.bind(this);
    this.callGraph = this.callGraph.bind(this);
    this.getItemRestId = this.getItemRestId.bind(this);

    this.state = { graphResponse: "", accessToken: "" };
    console.log("====GetItemOJSHelpers loaded");
  }

  getEmail() {
    console.log("====getEmail(): Entered ");

    //debugger;
    // Get the access token and create a Microsoft Graph client
    let authenticator = new Authenticator();

    // register Microsoft (Azure AD 2.0 Converged auth) endpoint
    authenticator.endpoints.registerMicrosoftAuth("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", {
      redirectUrl: "https://localhost:3000/index.html",
      scope: "Mail.ReadWrite User.Read User.ReadBasic.All"
    });

    console.log("====getEmail(): Getting token");

    let authObject = authenticator.tokens.get("Microsoft");
    let accessToken = authObject.access_token;

    if (accessToken !== null) {
      console.log(`====getEmail(): Current cached token: ${accessToken}`);
      this.callGraph(accessToken);
      return;
    } else {
      // for the default Microsoft endpoint
      //If the user, rejects the grant to the application then you will receive an error in the catch function.
      authenticator
        .authenticate(DefaultEndpoints.Microsoft)
        .then(function(token) {
          /* Microsoft Token */
          console.log(`====getEmail(): Authenticated; auth token: ${token.access_token}`);
          accessToken = token.access_token;
        })
        .catch(function(error) {
          //debugger;
          console.log("====getEmail(): authenticate error");
          Utilities.log(error);
          throw new Error("Failed to login using your Office 365 Account");
        });
    }

    console.log(`====getEmail(): Current token: ${accessToken}`);
    this.callGraph(accessToken);
  }

  callGraph(token) {
    // Get the item's REST ID
    let itemId = this.getItemRestId();
    console.log(`====callGraph(): itemId ${itemId}`);

    const client = Graph.Client.init({
      authProvider: done => {
        done(null, token); //first parameter takes an error if you can't get an access token
      },
      debugLogging: true
    });

    client
      .api("me/messages/" + itemId)
      .version("v1.0")
      .get()
      .then(function(item) {
        //debugger;
        console.log("Email " + item.Subject + " retrieved!!!");
      })
      .then(function() {
        console.log("====callGraph(): complete");
        //debugger;
      })
      .catch(err => {
        //debugger;
        //403 Forbidden! code: "ErrorAccessDenied", message: "Access is denied. Check credentials and try again."
        //Also 401 InvalidAuthenticationToken: Access token validation failure.
        console.log(`====callGraph(): error! ${err.statusCode}:'${err.code}': ${err.message}`);
      });
  }

  getItemRestId() {
    if (Office.context.mailbox.diagnostics.hostName === "OutlookIOS") {
      // itemId is already REST-formatted
      return Office.context.mailbox.item.itemId;
    } else {
      // Convert to an item ID for API v2.0
      return Office.context.mailbox.convertToRestId(
        Office.context.mailbox.item.itemId,
        Office.MailboxEnums.RestVersion.v2_0
      );
    }
  }
  render() {
    return (
      <div>
        <Button
          id="getEmailButton"
          className="ms-welcome__action ms-bgColor-red"
          buttonType={ButtonType.primary}
          onClick={this.getEmail}
        >
          Call Graph
        </Button>
        <div>
          <h3> Access Token </h3>
          <TextField id="accessToken" />
        </div>
        <div>
          <h3>Graph API Call Response</h3>
          <TextField id="graphResponse" />
        </div>
      </div>
    );
  }
}
Marc LaFleur
  • 31,987
  • 4
  • 37
  • 63
Eric Legault
  • 5,706
  • 2
  • 22
  • 38
  • Hi Eric, have you been able to verify that your code for calling into graph is built correctly on its own, without the add-in part? In other words, can you hardcode some item id into the part where you're trying to retrieve the email, hook this up to a simple page and see if that works? also, remember that you have to convert the item ids you get from our JS API to Rest ID using our convertToRestId function in Office.js. – Outlook Add-ins Team - MSFT Jun 20 '18 at 19:29
  • I was able to get this working. I don't recall the specifics, as I'm all over the map on this project and keep running into new issues, so it's hard to keep them straight lol. – Eric Legault Jun 22 '18 at 03:52

0 Answers0