Office 365 API – PHP, Node.js, etc での使用


Office 365 API

(Skype API は こちら)

こんにちは。

Microsoft Conference 2014 のセッション「オープン テクノロジによる Office 365 アプリの可能性」で紹介した PHP, Node.js のコードを以下に掲載します。

Microsoft Azure をご利用の方は、ローカル環境は必要なく、App Service Editor でコードを張り付ければ動作しますのでお試しください。(手順は「App Service Editor で Node.js アプリ開発」、「App Service Editor で PHP アプリ開発」を参照してください。)

なお、Visual Studio の Office 365 API Tools などライブラリーを使用した開発ステップについては、Code Recipe「Office 365 API Tools を使用したプログラミング」に掲載しました。

 

HTTP Flow (おさらい)

Office 365 API 入門」で紹介したように、Office 365 API を使用した認証・認可・API 呼び出しの流れは、シンプル、かつ、一般的な手法 (Facebook など他でも使用されている方法) にそった内容となっています。(セッションで紹介したように、これに、エンタープライズを意識した多くの価値を付加しています。)
このため、Android 用の Office 365 SDK なども提供されていますが、無理にこうしたライブラリーを使わなくても、開発者自身でフローを実装することで、多くの言語環境 (PHP, Node.js, Python, Ruby, など) で使用していただくことができます。

以下のフローをプログラムで実装します。(Client ID, Client Secret などの取得方法については「Office 365 API 入門」を参照してください。)

1. Azure AD にログインを要求します (下図の通り、ログイン画面が表示されます)

GET https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id={client id}&resource={resource id}&redirect_uri={redirect uri}

2. ログインが完了すると、auth code を伴って戻ってきます。アプリは、この URI フラグメントから auth code を取得します。

https://{redirect uri}/?code={auth code}

3. 取得した auth code を使って access token を取得します。(Response は Json で返ってきます。)

POST https://login.microsoftonline.com/common/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code={auth code}&client_id={client id}&client_secret={client secret}&redirect_uri={redirect uri}

4. 取得した access token を使って Exchange, SharePoint など Office 365 のサービスを呼び出します。下記は、Inbox のメッセージの一覧を取得しています。

GET https://outlook.office365.com/api/v1.0/me/messages
Accept: application/json
Authorization: Bearer {access token}

なお、実際のプログラミングでは、もちろん、エラー処理や、Refresh Token を使用した Access Token の再取得などの処理も必要ですが、以下のサンプル コードでは、デモ用に これらの処理は省略していますので注意してください。
こうした HTTP Flow の詳細 (例 : Refresh Token を使った Access Token の再取得、など) については、以前記載した「Azure AD を使った API (Service) 連携の Client 開発」を参照してください。

 

PHP のサンプル コード

以下は、上記のフローを PHP で実装したサンプルです。(Redirect Uri, Client Id, Client Secret は、適宜、変更してください。)
Exchange Online から Inbox のメール (Max 10 件) を取得し、その Subject の一覧を表示します。

<?php
// This time we use in-memory session,
// but please change if needed (scale not ready)
session_start();

if(isset($_GET['code'])) {

  // When auth code is supplied, get access token
  $authreq = curl_init();  
  curl_setopt($authreq, CURLOPT_URL,
    'https://login.microsoftonline.com/common/oauth2/token');
  curl_setopt($authreq, CURLOPT_POST, true);  
  curl_setopt($authreq, CURLOPT_HTTPHEADER,
    array('Content-Type: application/x-www-form-urlencoded'));
  curl_setopt($authreq, CURLOPT_POSTFIELDS, http_build_query(array(
    'grant_type' => 'authorization_code',
    'code' => $_GET['code'],
    'client_id' => 'da203772-d60c-43a9-8f05-9316a8addafb',
    'client_secret' => 'muMXQNh8JRZ. . .',
    'redirect_uri' => 'https://tsmatsuz-msc01.azurewebsites.net/',
  )));
  curl_setopt($authreq, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($authreq, CURLOPT_SSL_VERIFYPEER, false); // see below !
  $authres = json_decode(curl_exec($authreq));
  curl_close($authreq);

  $_SESSION['access_token'] = $authres->access_token;

}

// If initial access, jump to get auth code
if(!isset($_SESSION['access_token']))
  header('Location: https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id=da203772-d60c-43a9-8f05-9316a8addafb&resource='
    .urlencode('https://outlook.office365.com/')
    .'&redirect_uri='
    .urlencode('https://tsmatsuz-msc01.azurewebsites.net/'));

// Get messages from Office 365 (Exchange Online)
$datareq = curl_init();
curl_setopt($datareq, CURLOPT_URL,
  'https://outlook.office365.com/api/v1.0/me/messages?$top=10&$orderby=DateTimeSent%20desc&$select=Subject,DateTimeReceived,From');
curl_setopt($datareq, CURLOPT_HTTPHEADER,
  array('Authorization: Bearer '.$_SESSION['access_token']));
curl_setopt($datareq, CURLOPT_RETURNTRANSFER, true);
curl_setopt($datareq, CURLOPT_SSL_VERIFYPEER, false); // see below !
$results = json_decode(curl_exec($datareq));
curl_close($datareq);

// Display results
header('Content-type: text/html; charset=utf-8');
foreach($results->value as $entry) {
  echo $entry->Subject . '<br />';
}
?>

補足 : 上記で CURLOPT_SSL_VERIFYPEER を false に設定していますが、実際の開発では正しく証明書を添付しておいてください。(デモ目的の簡易設定です。)
Azure web site で curl を使って SSL (https) でアクセスする際の注意点」に記載しました。

 

Node.js のサンプル コード

同じ処理を Node.js でプログラミングしたサンプル コードを紹介します。
ここではフレームワークに依存しないコードにするため Express 等は使っていませんが、実際の開発では、Express やデータベースなどを使って access token を管理 (キャッシュなど) してください。
(同様に、Redirect Uri, Client Id, Client Secret は、適宜、変更してください。)

var http = require('http');
var https = require('https');
var url = require('url');
var qs = require('querystring');

http.createServer(function (req, res) {
  // This time we don't store access token,
  // but please store access token and reuse in production code...
  var query = url.parse(req.url, true).query;
  if('code' in query) {
    // Get access token
    getAccessToken(query.code, function(jsonAuth) {
      // Get messages from Office 365 (Exchange Online)
      res.writeHead(200,
     { 'Content-Type': 'text/html; charset=utf-8' });
      var msgbody = '';
      getExchangeMessage(JSON.parse(jsonAuth).access_token,
   function(jsonMsg) {
        msgbody += jsonMsg;
      }, function() {
        var msgobj = JSON.parse(msgbody).value;
        for(var i = 0; i < msgobj.length; i++) {
          var msg = msgobj[i];
          res.write(msg.Subject + '<br />');
        }
        res.end();
      });
    });
  }
  else {
    // Redirect to login
    res.writeHead(302, {
      'Location':
        'https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id=da203772-d60c-43a9-8f05-9316a8addafb&resource='
          + encodeURIComponent('https://outlook.office365.com/')
          + '&redirect_uri='
          + encodeURIComponent('https://tsmatsuz-msc01.azurewebsites.net/')
    });
    res.end();    
  }
}).listen(process.env.PORT);

function getAccessToken(code, callback) {
  var postdata = qs.stringify({
    'grant_type' : 'authorization_code',
    'code' : code,
    'client_id' : 'da203772-d60c-43a9-8f05-9316a8addafb',
    'client_secret' : 'muMXQNh8JRZ . . .',
    'redirect_uri' : 'https://tsmatsuz-msc01.azurewebsites.net/'
  });
  var opt = {
    host : 'login.windows.net',
    port : 443,
    path : '/common/oauth2/token',
    method : 'POST',
    headers : {
      'Content-Type' : 'application/x-www-form-urlencoded',
      'Content-Length': Buffer.byteLength(postdata)
    }
  };
  var authreq = https.request(opt, function(authres) {
    authres.setEncoding('utf-8');
    authres.on('data', callback);
  });
  authreq.write(postdata);
  authreq.end();
}

function getExchangeMessage(access_token, datacallback, endcallback) {
  var opt = {
    host : 'outlook.office365.com',
    port : 443,
    path : '/api/v1.0/me/messages?$top=10&$orderby=DateTimeSent%20desc&$select=Subject,DateTimeReceived,From',
    method : 'GET',
    headers : {
      'Authorization' : 'Bearer ' + access_token,
      'Content-Length': 0
    }
  };
  var o365req = https.request(opt, function(o365res) {
    o365res.setEncoding('utf-8');
    o365res.on('data', datacallback);
    o365res.on('end', endcallback);
  });
  o365req.end();
}

 

セッションで紹介した Excel VBA を使ったお遊びは「Office 365 API を Excel VBA から使う」に掲載しました。(イベント用の「遊び」で作ったコードなので、あまり真面目に考えないでください。Json のパースとかいろいろ考えると「結局、大部分は ScriptControl に渡しちゃえば良いじゃん」とか、いろいろ思うところは出てくると思いますが。。。)
また、セッションでも紹介した Zapier で使用されている Office 365 用の app は Python で書かれています。「Exchange dev blog – Zapier’s Office 365 API Journey」で紹介されています。

Office 365 API は多くの言語環境で使っていただくことができますので、是非お試しください。

 

※ 変更履歴 :

2016/07/19  Visual Studio Online (Monaco) から App Service Editor に名称変更

 

Comments (11)

  1. 理人 says:

    こちらを参考に現在Offce365APIの処理を開発しているのですが

    access_tokenを取得するところで現在エラーとなって困っています。

    "error":"invalid_request","error_description":"AADSTS90011: 'redirect_uri' value must be a valid absolute Uri

    上記エラーが表示されており、どうしたらいいかわかっていません。

    ※FormのPOSTなど行っています。

    PHPを参考にしたプログラムも使ってみたのですが、返却値がない状況です。

    何か助言があればト思い書き込みました。

    どうぞよろしくお願いします。

  2. どのような呼び出しでエラーが返ってきたか、参考までにコードなどを見せていただくことは可能ですか?(例えば、redirect_uri の箇所に渡す URL 文字列は、ちゃんと URL エンコードされてますでしょうか?)

  3. 理人 says:

    返信ありがとうございます。

    丸パクリではあるのですが・・・

    curlのレスポンスが現状空で返却されています。

    node.jsを使用しても試しはしたのですが、同じくPOSTの部分でエラーが表示されてしまいます。

    ※nodeの場合はgetaddrinfo ENOTFOUND

    proxyなども疑っていろいろ試したのですが、access_tokenを取得することができていません。

    ・phpのソース

    <?php

    if(isset($_GET['code'])) {

     error_log("code->".$_GET['code'].PHP_EOL, 3, "./test.log");

     // When auth code is supplied, get access token

     $authreq = curl_init();

     curl_setopt($authreq, CURLOPT_URL,

                 'login.microsoftonline.com/…/token&);

     curl_setopt($authreq, CURLOPT_POST, true);

     curl_setopt($authreq, CURLOPT_HTTPHEADER,

                 array('Content-Type: application/x-www-form-urlencoded'));

     curl_setopt($authreq, CURLOPT_POSTFIELDS, http_build_query(array(

       'grant_type' => 'authorization_code',

       'code' => $_GET['code'],

       'client_id' => 'client_id',

       'client_secret' => 'client_secret',

       'redirect_uri' => urlencode('http://localhost:3001/#/officetest&#39;),

     )));

     curl_setopt($authreq, CURLOPT_RETURNTRANSFER, true);

     curl_setopt($authreq, CURLOPT_SSL_VERIFYPEER, false);

     $request = curl_exec($authreq);

     $info = curl_getinfo($authreq);

     error_log("info : ".print_r($info,true).PHP_EOL, 3, "./test.log");

     error_log("request : ".$request.PHP_EOL, 3, "./test.log");

     error_log("request : ".print_r($request,true).PHP_EOL, 3, "./test.log");

     $authres = json_decode($request);

     curl_close($authreq);

     $_SESSION['access_token'] = $authres->access_token;

    }

    ・nodeのソース

    var opt = {

       uri: 'login.windows.net/…/token&,

    //    path: '/common/oauth2/token',

       port: '443',

       method: 'POST',

       form: {

             'grant_type' : 'authorization_code',

             'code' : code,

             'client_id' : '32026df5-2de8-43be-a8dd-22d1564ed594',

             'client_secret' : 'b0IlmHeUWgqvYeSudvNjn92fvvSRUrdf1o0niIJcj/c=',

             'redirect_uri' : 'http://localhost:3001/&#39;

           },

       headers : {

             'Content-Type' : 'application/x-www-form-urlencoded',

       }

     };

     request.get(opt, function (error, response, body) {

       console.log("error:"+error);

       console.log("response:"+response);

       console.log("body:"+body);

       if (response !== "undefined"){

         if (!error && response.statusCode == 200) {

           console.log("body:"+body[0]);

           response.render('index', {data:body});

         }

       }else{

         console.log('error: '+ response.statusCode);

       }

     });

  4. お世話になります。こちらで同じプログラムで確認してみましたが問題なく動作するようなので、再度確認させてください。

    まず、php に絞って質問させていただきますが、上記コードで code は、ちゃんと返ってきた値が URL に設定されていますよね?

    あと、コメント投稿時に勝手に変換されたのかもしれませんが、上記で、client_id, client_secret に値が設定されていないように見えますが、Azure AD のポータルから取得した client_id, client_secret を設定されていますか?

    あと、redirect_uri も "#" の入ったハッシュが設定されているようですが、Azure AD 登録時の Redirect URL (実際に存在するページ)を設定されておりますか?

    応答結果は、まったく空(HTTP Status なども返ってきていない)という状況でしょうか?

  5. 理人 says:

    返信ありがとうございます。

    codeについては、渡ってきた値をcode=に設定しています。

    ※ログなどで出力して確認済み

    client_id, client_secretの値についてもAzureから取得したものを設定しています。

    Azureで設定しているRedirectURLはlocalhostのものを設定しており、ローカル環境でのみ動作しています。

    localhost以外の外に出ているものを使用したほうがいいのでしょうか。

    ※Azureで設定しているのが自分ではない人なので、どのようにせってされているか詳細はわかっていませんが

    http://localhsot、http://localhost:3001を設定して試しましたが、正常に動作せしませんでした。

    返却値についてですが、FALSEがへんきゃされているます(cUrlの戻り値として)

    headerなどを出力してみましたが、lengthが0など正常に返却が行われていないように見えます。

    お手数ですが、お手すきの時にでもお願いいたします。

  6. Azure Web App (旧 Website) ではなく、ローカル環境で実施されているということですね。現象的にそもそも呼べていないような動きに見えます。(呼び出してエラーが返ってきているのではなく)

    例えば、login.microsoftonline.com/…/token に対して、空の Body のまま POST した場合は、ちゃんとエラーは取れますでしょうか?(この場合、Status 411 エラー、「HTTP Error 411. The request must be chunked or have a content length.」という内容の Body が帰ります)

    ローカル環境で実施されている場合、Fiddler でトレースしてエラーを取ることもできるかもしれません。

    あと、いろいろお試しいただきすみませんが、念のため、https (SSL) の URI を redirect uri として使用して頂けますでしょうか。

    よろしくお願いします。

  7. 理人 says:

    Fiddlerで見ながらやってみましたが、FiddlerにPOSTした時のパケットが表示されませんでした。

    また、FALSEがやはり返却されます。

    もしかすると正常にPOSTできていないのかもしれません。

    もう少しいろいろ見てはみます。

    ※POST空でも同じでした

  8. どうも呼び出し自体がエラーになっているようですね。

    何度もすみませんが、例えば、http や https を使った他のサイトの CURL による呼び出しをおこなった場合は問題なく返ってきますでしょうか?(エラーの取得も含め、現象が変化しますか?)もし他のサイトが問題ないなら証明書に問題がある可能性があるので、証明書を直接指定する方法を紹介します。

    あと、try – catch で返ってきているエラーの内容を取得することなどは可能でしょうか?

    try{

     $test = curl_exec(…);

    } catch (Exception $e) {

     echo 'Exception captured !';

     echo 'Exception is '.$e->getMessage();

    }

  9. 理人 says:

    こちらこそ、対応していただきありがとうございます。

    取り急ぎで、

    curlのエラーで「Could not resolve host: login.microsoftonline.com」となっていました。

    ほかのURLで試してみても同じでした。

    ※localhostの場合は問題ない状態です

  10. 理人 says:

    こちらproxy環境で行っていたので、そこで問題があるようにも考えられました。

    一度proxyなどない環境で試してみようかと思います。

  11. よろしくお願い致します。

Skip to main content