Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

office 365 graph api from javascript: how to properly authenticate

I don't hate Oauth, but I hate myself for not being able to wrap my head around the concept. Having said that, here's my question: I'm trying to call the Office Graph REST api from vanilla javascript. So I'm doing a regular XMLHttpRequest to graph.microsoft.com, from a page which runs on my SharePoint Online site (therefore the code should run from my security context, as I am logged in). The call returns a 403 Authentication Required. I assume I have to register an app in Azure AD and I have done that, so I have a Client ID and a secret. However, I cannot find what to do next programmatically (I think I understand the concept, I have to get a token that I have to provide when calling the Graph api). It seems there's tons of sample code for just about anything, with the exception of javascript. Does anyone have pointers?

Update: I know the involvement of the token, and that's the part I can't wrap my head around (see original question/comment); I have a Client ID, I have a secret, and I have this (VERY common) code:

function graphRead(whatToRead) { 
switch(whatToRead) {
    case "userinfo" :
        officeUser = JSON.Parse(loadXMLDoc("GET","https://graph.microsoft.com/v1.0/me"));
        break;
    default:
    };
};

function loadXMLDoc(mMethod,uURL) {
    var xmlhttp;

    if (window.XMLHttpRequest) {
        // code for IE7+, Firefox, Chrome, Opera, Safari
        xmlhttp = new XMLHttpRequest();
    } else {
        // code for IE6, IE5
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }

    xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == XMLHttpRequest.DONE ) {
           if(xmlhttp.status == 200){
               return(xmlhttp.responseText);
           }
           else if(xmlhttp.status == 400) {
              alert('There was an error 400')
           }
           else {
               alert('something else other than 200 was returned')
           }
        }
    };

    xmlhttp.open(mMethod, uURL, true);
    xmlhttp.send();
};

Question is: what do I need to do to establish the token and send it to the API?

like image 611
Robert de Graaff Avatar asked Jun 01 '16 16:06

Robert de Graaff


2 Answers

If you're doing this all client-side in JavaScript, you likely want to implement what's known as the "implicit grant" flow. Azure has a write up of the process here: https://azure.microsoft.com/en-us/documentation/articles/active-directory-v2-protocols-implicit/.

Basically your page would either have a "sign in" link or would automatically browse to the Azure authorization page with all of the parameters encoded into the URL like your client ID and the scopes you are requesting to the Graph. If needed, the user will have to log in, but in your case they may not have to. Once the user logs in with their credentials (again, if needed), they would be asked to grant consent to allow your app to access their data. Assuming they say yes, Azure would redirect back to your page with the access token in a query hash. You'd need to have JS code there to extract the access token from the hash. For example, the redirect would look something like:

https://localhost/myapp/#
access_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q...
&token_type=Bearer
&expires_in=3599
&scope=https%3a%2f%2fgraph.microsoft.com%2fmail.read 
&id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q...
&state=12345

Pseudo-steps

  1. On page load, check if there's already an access token in the URL fragment.
    1. If so, that indicates your loading after an authorization redirect, and you can proceed to make your Graph calls.
    2. If not, then you need to prompt the user or auto-redirect to the authorization endpoint.
  2. You need to pass the token in the Authorization http header when calling Graph. You can do this by adding the following line to your code (before the send): xmlhttp.setRequestHeader("Authorization", "Bearer " + token);

But why do I have to do this?

The answer is that OAuth is all about running as the app, not running as the user. So the old model of the app authenticating as the user doesn't apply. The user has to grant permissions to the app to access their data.

So really what's happening here isn't that the app is having to "auth again as the user", it has to auth as itself! That's really what your doing (by providing your client ID). As part of that process, the user may have to log in to confirm their identity, then provide consent.

Your client secret would not be used at all in the implicit flow. Essentially your app would "prove" it is really itself by using the client ID and being present at the URL you register as part of your app registration.

like image 94
Jason Johnston Avatar answered Oct 08 '22 06:10

Jason Johnston


The ADAL.js library will make life much easier for all OAuth authenticacion against Azure AD, including what you're trying to do using implicit OAuth consent from within a SharePoint js block.

  1. Register an app in your Azure AD applications section. Edit the manifest and activate implicit OAuth. Add "https://mytenant.sharepoint.com" to the app Reply Url list.
  2. Insert this JS code into a SharePoint Script Editor Webpart to authorize the user, get an access token and call the Graph API:

<script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.10/js/adal.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>

<body>
  <div><a href="#" onclick="login();return false;">login</a>
  </div>
  <div><a href="#" onclick="getToken();return false;">get access token and user info</a>
  </div>

</body>
<script type="text/javascript">
  var configOptions = {
    clientId: "insert Azure App ClientId here",
    postLogoutRedirectUri: window.location.origin,
  }

  var accessToken = null;

  window.authContext = new AuthenticationContext(configOptions);

  var isCallback = authContext.isCallback(window.location.hash);
  authContext.handleWindowCallback();

  function getToken() {
    authContext.acquireToken("https://graph.microsoft.com", function(error, token) {
      console.log(error);
      console.log(token);
      accessToken = token;
      getUsers();
    })
  }

  function login() {
    authContext.login();
  }

  function getUsers() {

    $.ajax({
      url: "https://graph.microsoft.com/v1.0/me",
      type: 'GET',
      headers: {
        "Authorization": "Bearer " + accessToken
      },
      crossDomain: true,
      success: function(response) {
        console.log(response);
        alert(response.userPrincipalName);
      }
    });

  }
</script>
like image 35
Lars Lynch Avatar answered Oct 08 '22 07:10

Lars Lynch