Talus
Talus

Reputation: 754

Retrieving user information with a oauth token from the office365 oauth api

I'm currently trying to use the Office365 REST APIs, and for that, I need to auth using the OAuth2 mechanism. So far, so good.

After a long battle, I finally managed to fetch the said tokens, but I need to fetch the user information (such as his identifier, email, name, ...) that goes with the token.

Using the sandbox, I need something like that :

enter image description here

I already tried to fetch the user information through the api.office.com api :

curl https://api.office.com/discovery/v1.0/me/services -H "Authorization: Bearer <oauth token>" -H "Accept: application/json;odata=verbose"

But all I have is the following :

{"error":{"code":"-2147024891, System.UnauthorizedAccessException","message":"Access denied. You do not have permission to perform this action or access this resource."}}

the request to get said OAuth token is based on these parameters (through the HWIOAuthBundle) :

<?php

$config = ['authorization_url' => 'https://login.windows.net/%s/oauth2/authorize',
           'infos_url' => 'https://outlook.office365.com/api/%s/me',
           'access_token_url' => 'https://login.windows.net/%s/oauth2/token',
           'application' => 'common',
           'api_version' => 'v1.0',
           'csrf' => true];

$config['access_token_url'] = sprintf($config['access_token_url'], $config['application']);
$config['authorization_url'] = sprintf($config['authorization_url'], $config['application']);
$config['infos_url'] = sprintf($config['infos_url'], $config['api_version']);

so any idea how could I get the user information (even the basic ones) ?

Thanks

-- edit

I think I got it. It would seems that with a curl request on https://outlook.office365.com/api/v1.0/me gives a simplistic array with the MailboxGuid, Id, Alias and GivenName :

curl https://outlook.office365.com/api/v1.0/me -X GET -H "Authorization: Bearer <my oauth token>"

Gives the following (with sometimes a timeout, sometimes not... I guess I have to work on that, if anyone can have suggestions, feel free to barge in)

{"@odata.context":"https://outlook.office365.com/api/v1.0/$metadata#Me",
 "@odata.id":"https://outlook.office365.com/api/v1.0/Users('[email protected]')",
 "Id":"[email protected]",
 "DisplayName":"Baptiste Clavi\u00e9",
 "Alias":"baptiste",
 "MailboxGuid":"<snip>"}

It is not as complete as the thing returned by the sandbox, but it should gives me what I need...

But... it sometimes gives me a timeout, sometimes it goes just fine... On a 3/5 ratio approximatively. Any idea ? Thanks

P.S : if you need to know how I configured the app on azure, do ask

Upvotes: 4

Views: 6080

Answers (2)

mehmetsen80
mehmetsen80

Reputation: 737

By looking many sources around.. I improved the php office365 client here

https://github.com/mehmetsen80/office365client

Assume you have a config such as this:

Config.php

<?php
/**
 * Created by PhpStorm.
 * User: msen
 * Date: 3/10/16
 * Time: 11:55 AM
 */
global $apiConfig;
$apiConfig = array(
    'oauth2_client_id' => '',//assign your own office 365 app client id
    'oauth2_secret' => '',  // Generate key from Azure Management Portal
    'oauth2_redirect' => 'http://website.com/office365client/oauth2.php',   //example url
    'state' => '45d12e60b-8457-4d99-b20f-cfb612d1a138',  //any unquiue key to Check against CSRF attack
    'resource' => 'https://outlook.office365.com',
    'oauth2_auth_url' => 'https://login.windows.net/common/oauth2/authorize',
    'oauth2_token_url' => 'https://login.windows.net/common/oauth2/token',
);
?>

At the end you'll have such a code..

session_start();
    $client = new Office365_Client();
    $forward_url = $client->createAuthUrl();
    $code = $_GET['code'];
    if(isset($_GET['code'])) {
        $client->setCode($code);
        $client->fetchTokens(); //get tokens
        $client->fetchJWT();//let's get user info
        //put the user token info into sessions
        $_SESSION['name'] = $client->getJwt()->getName();//full name of the user
        $_SESSION['unique_name'] = $client->getJwt()->getUniqueName();//could be email or id from office365
        $_SESSION['tid'] = $client->getJwt()->getTid();//tenant id
    }else{
        header( 'Location: '.$forward_url ); //redirect automatically on the first page visit, 2nd page visit will get the $code
    }

Microsoft tells us what user info can be retrieved.. https://msdn.microsoft.com/library/office/dn707383.aspx

JWT.php

    <?php

/**
 * Created by PhpStorm.
 * User: msen
 * Date: 3/10/16
 * Time: 12:04 PM
 */
class JWT
{
    private $aud;
    private $iss;
    private $iat;
    private $nbf;
    private $exp;
    private $ver;
    private $tid;
    private $amr;
    private $oid;
    private $upn;
    private $puid;
    private $sub;
    private $given_name;
    private $family_name;
    private $name;
    private $unique_name;
    private $appid;
    private $appidacr;
    private $scp;
    private $acr;

    public function __construct($jwt_arr){
        $this->aud = $jwt_arr['aud'];
        $this->iss = $jwt_arr['iss'];
        $this->iat = $jwt_arr['iat'];
        $this->nbf = $jwt_arr['nbf'];
        $this->exp = $jwt_arr['exp'];
        $this->ver = $jwt_arr['ver'];
        $this->tid = $jwt_arr['tid'];
        $this->amr = $jwt_arr['amr'];
        $this->oid = $jwt_arr['oid'];
        $this->upn = $jwt_arr['upn'];
        $this->puid = $jwt_arr['puid'];
        $this->sub = $jwt_arr['sub'];
        $this->given_name = $jwt_arr['given_name'];
        $this->family_name = $jwt_arr['family_name'];
        $this->name = $jwt_arr['name'];
        $this->unique_name = $jwt_arr['unique_name'];
        $this->appid = $jwt_arr['appid'];
        $this->appidacr = $jwt_arr['appidacr'];
        $this->scp = $jwt_arr['scp'];
        $this->acr = $jwt_arr['acr'];
    }

    /**
     * @return mixed
     */
    public function getAud()
    {
        return $this->aud;
    }

    /**
     * @param mixed $aud
     */
    public function setAud($aud)
    {
        $this->aud = $aud;
    }

    /**
     * @return mixed
     */
    public function getIss()
    {
        return $this->iss;
    }

    /**
     * @param mixed $iss
     */
    public function setIss($iss)
    {
        $this->iss = $iss;
    }

    /**
     * @return mixed
     */
    public function getIat()
    {
        return $this->iat;
    }

    /**
     * @param mixed $iat
     */
    public function setIat($iat)
    {
        $this->iat = $iat;
    }

    /**
     * @return mixed
     */
    public function getNbf()
    {
        return $this->nbf;
    }

    /**
     * @param mixed $nbf
     */
    public function setNbf($nbf)
    {
        $this->nbf = $nbf;
    }

    /**
     * @return mixed
     */
    public function getExp()
    {
        return $this->exp;
    }

    /**
     * @param mixed $exp
     */
    public function setExp($exp)
    {
        $this->exp = $exp;
    }

    /**
     * @return mixed
     */
    public function getVer()
    {
        return $this->ver;
    }

    /**
     * @param mixed $ver
     */
    public function setVer($ver)
    {
        $this->ver = $ver;
    }

    /**
     * @return mixed
     */
    public function getTid()
    {
        return $this->tid;
    }

    /**
     * @param mixed $tid
     */
    public function setTid($tid)
    {
        $this->tid = $tid;
    }

    /**
     * @return mixed
     */
    public function getAmr()
    {
        return $this->amr;
    }

    /**
     * @param mixed $amr
     */
    public function setAmr($amr)
    {
        $this->amr = $amr;
    }

    /**
     * @return mixed
     */
    public function getOid()
    {
        return $this->oid;
    }

    /**
     * @param mixed $oid
     */
    public function setOid($oid)
    {
        $this->oid = $oid;
    }

    /**
     * @return mixed
     */
    public function getUpn()
    {
        return $this->upn;
    }

    /**
     * @param mixed $upn
     */
    public function setUpn($upn)
    {
        $this->upn = $upn;
    }

    /**
     * @return mixed
     */
    public function getPuid()
    {
        return $this->puid;
    }

    /**
     * @param mixed $puid
     */
    public function setPuid($puid)
    {
        $this->puid = $puid;
    }

    /**
     * @return mixed
     */
    public function getSub()
    {
        return $this->sub;
    }

    /**
     * @param mixed $sub
     */
    public function setSub($sub)
    {
        $this->sub = $sub;
    }

    /**
     * @return mixed
     */
    public function getGivenName()
    {
        return $this->given_name;
    }

    /**
     * @param mixed $given_name
     */
    public function setGivenName($given_name)
    {
        $this->given_name = $given_name;
    }

    /**
     * @return mixed
     */
    public function getFamilyName()
    {
        return $this->family_name;
    }

    /**
     * @param mixed $family_name
     */
    public function setFamilyName($family_name)
    {
        $this->family_name = $family_name;
    }

    /**
     * @return mixed
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param mixed $name
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * @return mixed
     */
    public function getUniqueName()
    {
        return $this->unique_name;
    }

    /**
     * @param mixed $unique_name
     */
    public function setUniqueName($unique_name)
    {
        $this->unique_name = $unique_name;
    }

    /**
     * @return mixed
     */
    public function getAppid()
    {
        return $this->appid;
    }

    /**
     * @param mixed $appid
     */
    public function setAppid($appid)
    {
        $this->appid = $appid;
    }

    /**
     * @return mixed
     */
    public function getAppidacr()
    {
        return $this->appidacr;
    }

    /**
     * @param mixed $appidacr
     */
    public function setAppidacr($appidacr)
    {
        $this->appidacr = $appidacr;
    }

    /**
     * @return mixed
     */
    public function getScp()
    {
        return $this->scp;
    }

    /**
     * @param mixed $scp
     */
    public function setScp($scp)
    {
        $this->scp = $scp;
    }

    /**
     * @return mixed
     */
    public function getAcr()
    {
        return $this->acr;
    }

    /**
     * @param mixed $acr
     */
    public function setAcr($acr)
    {
        $this->acr = $acr;
    }


    public function toString(){

        return "JWT ==> <br/>
        aud: ".$this->aud."<br/>
        iss: ". $this->iss ."<br/>
        iat: ". $this->iat ."<br/>
        nbf: ". $this->nbf ."<br/>
        exp: ". $this->exp ."<br/>
        ver: ". $this->ver ."<br/>
        tid: ". $this->tid ."<br/>
        amr pwd: ". $this->amr->pwd ."<br/>
        oid: ". $this->oid ."<br/>
        upn: ". $this->upn ."<br/>
        puid: ". $this->puid ."<br/>
        sub: ". $this->sub ."<br/>
        given_name: ". $this->given_name ."<br/>
        family_name: ". $this->family_name ."<br/>
        name: ". $this->name ."<br/>
        unique_name: ". $this->unique_name ."<br/>
        appid: ". $this->appid ."<br/>
        appidacr: ". $this->appidacr ."<br/>
        scp: ". $this->scp ."<br/>
        acr: ". $this->acr;
    }

}

Here is how JWT array really looks like

/********
     * Another Example JWT from microsoft site  https://msdn.microsoft.com/library/office/dn707383.aspx
     *
     * {
    "aud": "https://manage.office.com",
    "iss": "https://sts.windows.net/41463f53-8812-40f4-890f-865bf6e35190/",
    "iat": 1427246416,
    "nbf": 1427246416,
    "exp": 1427250316,
    "ver": "1.0",
    "tid": "41463f53-8812-40f4-890f-865bf6e35190",
    "amr": [
    "pwd"
    ],
    "oid": "1cef1fdb-ff52-48c4-8e4e-dfb5ea83d357",
    "upn": "[email protected]",
    "puid": "1003BFFD8EC47CA6",
    "sub": "7XpD5OWAXM1OWmKiVKh1FOkKXV4N3OSRol6mz1pxxhU",
    "given_name": "John",
    "family_name": "Doe",
    "name": "Contoso, Inc.",
    "unique_name": "[email protected]",
    "appid": "a6099727-6b7b-482c-b509-1df309acc563",
    "appidacr": "1",
    "scp": "ActivityFeed.Read ServiceHealth.Read",
    "acr": "1"
    }
     *
     *
     */

HttpPost.php

<?php
/**
 * Created by PhpStorm.
 * User: msen
 * Date: 3/10/16
 * Time: 12:13 PM
 */
class HttpPost
{
    public $url;
    public $postString;
    public $httpResponse;
    public $ch;
    public function __construct($url) {
        $this->url = $url;
        $this->ch = curl_init ( $this->url );
        curl_setopt ( $this->ch, CURLOPT_FOLLOWLOCATION, false );
        curl_setopt ( $this->ch, CURLOPT_HEADER, false );
        curl_setopt ( $this->ch, CURLOPT_RETURNTRANSFER, true );
        curl_setopt ( $this->ch, CURLOPT_SSL_VERIFYPEER, false );
    }
    public function __destruct() {
        curl_close ( $this->ch );
    }
    public function setPostData($params) {
        // http_build_query encodes URLs, which breaks POST data
        $this->postString = rawurldecode ( http_build_query ( $params ) );
        curl_setopt ( $this->ch, CURLOPT_POST, true );
        curl_setopt ( $this->ch, CURLOPT_POSTFIELDS, $this->postString );
    }
    public function send() {
        $this->httpResponse = curl_exec ( $this->ch );
    }
    public function getHttpResponse() {
        return $this->httpResponse;
    }
}

Office365_Client.php

<?php
/**
 * Created by PhpStorm.
 * User: msen
 * Date: 3/10/16
 * Time: 11:58 AM
 */
require_once "config.php";
require_once "JWT.php";
require_once "HttpPost.php";
class Office365_Client
{
    private $code;
    private $accessToken;
    private $refreshToken;
    private $id_token;
    private $jwt;
    public function __construct($config = array()) {
        global $apiConfig;
        $apiConfig = array_merge ( $apiConfig, $config );
    }
    public function createAuthUrl() {
        global $apiConfig;
        $query_params = array ('response_type' => 'code','client_id' => $apiConfig ['oauth2_client_id'],'client_secret' => $apiConfig ['oauth2_secret'],'redirect_uri' => $apiConfig ['oauth2_redirect'],'resource' => $apiConfig ['resource'],'state' => $apiConfig ['state']
        );
        $auth_url = $apiConfig ['oauth2_auth_url'] . '?' . http_build_query ( $query_params );
        return $auth_url;
    }
    public function fetchTokens() {
        global $apiConfig;
        $url = $apiConfig['oauth2_token_url'];
        $params = array ("code" => $this->code,"client_id" => $apiConfig ['oauth2_client_id'],"client_secret" =>$apiConfig ['oauth2_secret'],"resource" => $apiConfig ['resource'],"redirect_uri" => $apiConfig ['oauth2_redirect'],"grant_type" => "authorization_code"
        );
        // build a new HTTP POST request
        $request = new HttpPost ( $url );
        $request->setPostData ( $params );
        $request->send();
        $responseObj = json_decode($request->getHttpResponse ());
        $this->accessToken = $responseObj->access_token;
        $this->refreshToken = $responseObj->refresh_token;
        $this->id_token = $responseObj->id_token;
    }
    // Fetches JWT returned from Azure to get the user's info
    public function fetchJWT() {
        $token_parts = explode(".", $this->getIdToken());
        // First part is header, which we ignore
        // Second part is JWT, which we want to parse
        // First, in case it is url-encoded, fix the characters to be
        // valid base64
        $encoded_token = str_replace('-', '+', $token_parts[1]);
        $encoded_token = str_replace('_', '/', $encoded_token);
        // Next, add padding if it is needed.
        switch (strlen($encoded_token) % 4){
            case 0:
                // No pad characters needed.
                break;
            case 2:
                $encoded_token = $encoded_token."==";
                error_log("Added 2: ".$encoded_token);
                break;
            case 3:
                $encoded_token = $encoded_token."=";
                error_log("Added 1: ".$encoded_token);
                break;
            default:
                // Invalid base64 string!
                return null;
        }
        $json_string = base64_decode($encoded_token);
        $jwt_arr = json_decode($json_string, true);
        $this->jwt = new JWT($jwt_arr);
    }
    /**
     * @return mixed
     */
    public function getCode()
    {
        return $this->code;
    }
    /**
     * @param mixed $code
     */
    public function setCode($code)
    {
        $this->code = $code;
    }
    /**
     * @return mixed
     */
    public function getAccessToken()
    {
        return $this->accessToken;
    }
    /**
     * @param mixed $accessToken
     */
    public function setAccessToken($accessToken)
    {
        $this->accessToken = $accessToken;
    }
    /**
     * @return mixed
     */
    public function getRefreshToken()
    {
        return $this->refreshToken;
    }
    /**
     * @param mixed $refreshToken
     */
    public function setRefreshToken($refreshToken)
    {
        $this->refreshToken = $refreshToken;
    }
    /**
     * @return mixed
     */
    public function getIdToken()
    {
        return $this->id_token;
    }
    /**
     * @param mixed $id_token
     */
    public function setIdToken($id_token)
    {
        $this->id_token = $id_token;
    }
    /**
     * @return JWT
     */
    public function getJwt()
    {
        return $this->jwt;
    }
    /**
     * @param JWT $jwt
     */
    public function setJwt($jwt)
    {
        $this->jwt = $jwt;
    }
    public function toString(){
        return "Office365 ==> <br/>
                code: ". $this->code ."<br/>".
                "accessToken: ". $this->accessToken ."<br/>".
                "refreshToken: ".$this->refreshToken ."<br/>";
    }
}

And this is the file you are interacting with Office 365

oauth2.php

<?php
/**
 * Created by PhpStorm.
 * User: msen
 * Date: 3/10/16
 * Time: 12:25 PM
 */

require_once('Office365_Client.php');
session_start();
$client = new Office365_Client();
$forward_url = $client->createAuthUrl();
if(isset($_GET['code'])) {
    //TODO: verfiy unquie key state to check CSRF attack

    $code = $_GET['code'];
    $client->setCode($code);
    //get tokens
    $client->fetchTokens();
    echo '<br/><br/>';
    //print access tokens
    print($client->toString());
    echo '<br/><br/>';


    //you can set the tokens into your own session
    $_SESSION['accesstoken'] = $client->getAccessToken();
    $_SESSION['refreshtoken'] = $client->getRefreshToken();

    //let's get user info
    $client->fetchJWT();
    //print the usr info
    print($client->getJwt()->toString());


    //put the user token info into sessions
    $_SESSION['name'] = $client->getJwt()->getName();//full name of the user
    $_SESSION['unique_name'] = $client->getJwt()->getUniqueName();//could be email or id from office365
    $_SESSION['tid'] = $client->getJwt()->getTid();//tenant id


} else{
    //instead of putting a button, you can forward automatically yourself
    print "<a class='login' href='$forward_url'>Connect Me!</a>";

    //you can also redirect automatically
    //header( 'Location: '.$forward_url );
}

?>

Upvotes: 0

Jason Johnston
Jason Johnston

Reputation: 17702

Have you looked at the identity token that comes back from your token request? This might have all the info you want, and you already have it so no need to make a second request.

Here's a sample that (among other things) parses the identity token to get the user's display name. Check the getUserName function in Office365Service.php:

// Parses an ID token returned from Azure to get the user's
// display name.
public static function getUserName($id_token) {
  $token_parts = explode(".", $id_token);

  // First part is header, which we ignore
  // Second part is JWT, which we want to parse
  error_log("getUserName found id token: ".$token_parts[1]);

  // First, in case it is url-encoded, fix the characters to be 
  // valid base64
  $encoded_token = str_replace('-', '+', $token_parts[1]);
  $encoded_token = str_replace('_', '/', $encoded_token);
  error_log("After char replace: ".$encoded_token);

  // Next, add padding if it is needed.
  switch (strlen($encoded_token) % 4){
    case 0:
      // No pad characters needed.
      error_log("No padding needed.");
      break;
    case 2:
      $encoded_token = $encoded_token."==";
      error_log("Added 2: ".$encoded_token);
      break;
    case 3:
      $encoded_token = $encoded_token."=";
      error_log("Added 1: ".$encoded_token);
      break;
    default:
      // Invalid base64 string!
      error_log("Invalid base64 string");
      return null;
  }

  $json_string = base64_decode($encoded_token);
  error_log("Decoded token: ".$json_string);
  $jwt = json_decode($json_string, true);
  error_log("Found user name: ".$jwt['name']);
  return $jwt['name'];
}

Upvotes: 2

Related Questions