Native Application で SharePoint Online に Login して REST サービスなどを呼び出すプログラミング (Authentication)

2014/05 追記 : 最新の Native Application 開発には、Office 365 API が使用できます。「Office 365 API 入門」を参照してください。(この記事は古い投稿です。)

こんにちは。

今回は、Native App で SharePoint Online にログインして、SharePoint の REST API などを呼び出す方法を解説します。

ここで紹介する手法を使うと、Windows 8 store app や iOS app (iPhone, iPad)、Android app などから、SharePoint Online のサイトにアクセスできます。(ただし、client id, client secret が必要なため、事前に SharePoint Store に SharePoint アドインを登録して、このアドインをサイトにインストールします。 )

追記 : .NET のアプリケーション (Windows のデスクトップ・アプリケーション、ASP.NET のサーバー サイド・アプリケーションなど) では、「Office 365 SharePoint Online のプログラミングによる認証」で記述したように、wininet.dll を使用したブラウザー連携や、WS-Trust (ブラウザー不要)、SharePoint 2013 の .NET CSOM (ブラウザー不要) などの手法が利用できます。

追記 : Windows Phone 7.5 以上では、Windows Phone 用の SharePoint SDK に含まれる Silverlight Client Object Model (ClientContext) の Authenticator クラス (または ODataAuthenticator クラス) を使用して、SharePoint Online と連携したアプリケーションを容易に開発できます。

 

背景と概要 (Background)

Office 365 の認証基盤として Azure Active Directory (AAD) が使用され、「Azure AD を使った API (Service) 連携の Client 開発」で記載したように、外部の custom application から Login 画面を表示して access token を取得し、Azure Active Directory を使用している (OAuth 認可が必要な) 別のサービスを呼び出すといった開発が可能です。

ここまで読むと、SharePoint Online でも、カスタム認証をおこなって、SharePoint Online の REST サービスなどをバックエンドで呼び出すアプリが、同じように構築できるだろうと思うでしょう. . .

はい、確かに、SharePoint Online でも、同じ OAuth 2 のフローによってこうしたことが実現できます。
しかし、実は、SharePoint Online が使用している仕組みは、Azure Active Directory (AAD) そのものではなく、AAD の Access Control Service (ACS) を使った独自な仕組みが使われています。考えてみれば、AAD のこの仕組みは現在 Preview 版で、SharePoint Online のほうはとっくに正式リリースされてますので、当然かもしれませんね。。。

例えば、SharePoint アドインをテナントにインストールしても、Graph Explorer を使って https://graph.windows.net/<tenant realm>/applications を見るとわかりますが、AAD Graph の client application として登録されていません。Permission 構成など (つまり、「どのアプリでどのサイトにアクセスできるか」などの情報) は、SharePoint Online が独自に持っています。このため「Azure AD を使った API (Service) 連携の Client 開発」で解説したフローで SharePoint アプリの client_id でログインして code を取得しようとしても、Permission が構成されていないという警告が表示されます。(仮に code を取得できたとしても、その後のフローも動作しないでしょう。)

結論から言うと、SharePoint Online の場合は、同じフロー (OAuth のフロー) で、別のエンドポイントを使用する必要があります。
以降では、その流れを解説します。

補足 : なお、SharePoint アドインの通常の認証フローでは、SharePoint Online (Office 365) にログインをおこない、SharePoint の画面の中からカスタム・アプリ (SharePoint アドイン) を呼び出しますが、この時に、SharePoint Online が Refresh Token を POST して渡しています。(このフローについては、「SharePoint Add-ins : .NET CSOM を使ったプログラミングと認証」を参照してください。) よって、この仕組みを使って外部のアプリケーション (SharePoint の外の Native application など) から access token を取得することは不可能です。

 

事前準備 (Preparation)

まず、SharePoint アドインを作成して、SharePoint Online のサイトにインストールしておきましょう。このアプリの Credential を使って、SharePoint にアクセスするためのトークンを取得するためです。(ユーザーは、このアプリが持つ権限の範囲の操作しかできませんので、「SharePoint アドインの動作と概要」に従って適切な権限要求を設定しておいてください。)

また、後述するように、登録した SharePoint アドインの Client id, Client Secret, Redirect Uri が必要になります。

SharePoint Store から登録するアドインの場合、Seller Dashboard で作成された Client id と Client Secret を使用します。

また、各企業ごとに個別にインストールするアドインの場合には、あらかじめ https://{your sharepoint site url}/_layouts/15/appregnew.aspx にアクセスして、下図の通り Client id, Client Secret, Redirect Uri を設定しておきます。(また、権限設定を SharePoint アドインでおこなわず、マニュアルでおこなう場合には、/_layouts/15/appinv.aspx で設定します。今回、この詳細の入力内容は省略します。)

 

HTTP Flow

では、Native application がトークンを取得するまでの HTTP の流れを見てみましょう。

まず、大まかな流れですが、Native application などが Access token を取得するために使用可能なフローは、Azure Active Directory (Azure AD) も SharePoint Online (Azure AD の ACS) も同様で、以下の通りとなります。(OAuth のフローです。)

  1. code を取得します。この際、サインインの画面が表示されますので、ユーザーは ID / パスワードを入力します。
  2. 上記の code を使って、token (access token と refresh token) を取得します。
  3. access token を使ってサービスなどに接続します。(サービス側では、このトークンが妥当か検証します。)
    今回の場合、SharePoint Online の REST サービスを呼び出します。
  4. access token が期限切れになった場合、上記で取得した refresh token から access token を取りなおします。(この解説は、今回省略します。通常の Apps for SharePoint でも、この処理をおこなっていますので、参考にしてください。)

まずは、code の取得です。

code を取得するには、Web ブラウザー・コンポーネントなどを使って https://<site url>/_layouts/15/OAuthAuthorize.aspx?response_type=code&client_id=<client id>&scope=<以降で説明>&redirect_uri=<redirect url> の URL にアクセスします。
下記のようなフォーマットです。

GET https://o365demo01.sharepoint.com/sites/test1/_layouts/15/OAuthAuthorize.aspx?response_type=code&client_id=c8c0fa74-629e-47da-b2ce-08a3d89716d1&scope=&redirect_uri=http://localhost:44328/

上記の OAuth の scope は、通常はブランクで構いません。(むしろ、ここを指定した場合、ログインするユーザーにサイトの管理権限が必要なので、指定しないほうが良いでしょう。) インストールされた SharePoint アドインの権限 (Permission) に加えて、この Native Application に追加で権限 (Permission) を付与する場合のみ、scope を設定します。なお、設定する際は、「Web.Read」(Web の Read 権限の要求) などのフォーマット (複数指定する場合は、空白で区切る) を指定します。(SharePoint アドインの権限については、「SharePoint アドインの動作と概要」を参照してください。)

上記の URL に接続すると、下図の通り、Office 365 の SignIn 画面が表示されます。(下図は、WebBrowser コントロールを使って Windows のデスクトップ・アプリケーションでホストしているサンプルです。)

上図でユーザー ID とパスワードを入力して [サインイン] ボタンを押すと、下図のコンセント UI (Consent UI) が表示されます。
もし、上記で scope を指定 (追加) した場合は、下図の赤い囲みの通り、「その権限をアプリに付与しても良いか ?」をユーザー (利用者) に確認します。(この場合は、ユーザーにサイトの管理者権限が必要です。)

ユーザー (利用者) は、このアプリが信頼されるものであれば [信頼する] ボタンを押します。

補足 : なお、上記のコンセント UI で「信頼する」ボタンを押しても、Microsoft Account、Google Account などのように、アイデンティティ基盤の「信頼済みサイト」には登録されません。このため、ログイン時に、毎回、このコンセント UI が表示されるようです。

一連のログイン処理が成功すると、ブラウザーは、上記の redirect_uri にしたがって、以下のフォーマットでリダイレクトしようとします。(下記は、redirect_uri が http://localhost:44328/ であると仮定します。)
Native Application 側では、この Navigate のイベントを取得して Uri を取得し、そこから code を取得することができます。

http://localhost:44328/?code=IAAAAHqOz1ihMV...

つぎに、上記で取得した code を使って token (access token, refresh token) を取得します。
この処理は、以下のような HTTP の POST 要求によって取得できます。

POST https://accounts.accesscontrol.windows.net/<tenant realm>/tokens/OAuth/2
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&client_id=<client id>@<tenant realm>&client_secret=<client secret>&code=<上記で取得した code>&redirect_uri=<上記と同じ redirect uri>&resource=00000003-0000-0ff1-ce00-000000000000/<sharepoint host>@<tenant realm>

上記で、00000003-0000-0ff1-ce00-000000000000 は、Windows PowerShell で見るとわかりますが、SharePoint Online の Application Id (AppPrincipalId) です。(固定の値です。)
また、body に設定する Form-UrlEncoded の各値は、Url Encode が必要ですので注意してください。例えば、00000003-0000-0ff1-ce00-000000000000/<sharepoint host>@<tenant realm> は、実際には、00000003-0000-0ff1-ce00-000000000000%2F<sharepoint host>%40<tenant realm> のフォーマットになります。
client id、client secret は、SharePoint アプリによって決められた値になるので、皆さんのアプリにあわせて設定してください。

例えば、以下のような感じになります。以下で、「o365demo01.onmicrosoft.com」は tenant realm です。

POST https://accounts.accesscontrol.windows.net/o365demo01.onmicrosoft.com/tokens/OAuth/2
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&client_id=c8c0fa74-629e-47da-b2ce-08a3d89716d1%40o365demo01.onmicrosoft.com&client_secret=fMeStxeorB...&code=IAAAAHqOz1ihMV...&redirect_uri=http%3A%2F%2Flocalhost%3A44328%2F&resource=00000003-0000-0ff1-ce00-000000000000%2Fo365demo01.sharepoint.com%40o365demo01.onmicrosoft.com

この POST 要求をおこなうと、Response の Body として、下記の通り、Json フォーマットで access token や refresh token の値が返ってきます。(下記の「16d103a1-a264-4d36-9b52-51fa01ce5c2e」は tenant id です。tenant id については、「Azure Active Directory と事前準備」を参照してください。)

{
  "token_type":"Bearer",
  "access_token":"eyJ0eXAiOiJKV...",
  "expires_in":"43199",
  "refresh_token":"IAAAAOFn8Ofm...",
  "not_before":"1374627310",
  "expires_on":"1374670510",
  "resource":"00000003-0000-0ff1-ce00-000000000000/o365demo01.sharepoint.com@16d103a1-a264-4d36-9b52-51fa01ce5c2e"
}

あとは、「SharePoint Add-ins : .NET CSOM を使ったプログラミングと認証」で解説したように、HTTP の Authorization ヘッダーにこのトークンを設定して、SharePoint 2013 の新しい REST API などを呼び出すことができます。(なお、アプリの権限の範囲でしか操作できませんので、注意してください。)

GET https://o365demo01.sharepoint.com/sites/test1/_api/web/title
Accept: application/json; odata=verbose
Authorization: Bearer eyJ0eXAiOiJKV...

例えば、上記の場合、以下の通り Response が返ります。(SharePoint 2013 の新しい REST です。)

{
  "d":
  {
    "Title":"u30c6u30b9u30c8u30b5u30a4u30c8uff11"
  }
}

 

Programming の例

ですので、Native Application では、これをプログラムとして記述するだけです。

例えば、以下は、C# を使った WPF のアプリケーションとして実装したサンプル コードです。(Json の Parse のために、Json.NET を使用しています。)
なお、これは可読優先のサンプルですので、「.Result」などと書いている部分など、絶対に真似しないでくださいね。(「ASP.NET の非同期でありがちな Deadlock を克服する」を参照。)

. . .
using System.Web;
using System.Net.Http;
using System.Net.Http.Headers;
using Newtonsoft.Json.Linq;
. . .

//
// programming of OAuth flow (server-to-server interaction)
//

private void Button_Click(object sender, RoutedEventArgs e)
{
  // Step1 : Display browser (SignIn UI) and get code
  webBrowser1.Navigating += this.webBrowser1_Navigating;

  webBrowser1.Navigate(
    string.Format("{0}/_layouts/15/OAuthAuthorize.aspx?response_type=code&client_id={1}&scope={2}&redirect_uri={3}",
    "https://o365demo01.sharepoint.com/sites/test1", // Url of sharepoint site
    HttpUtility.UrlEncode("c8c0fa74-629e-47da-b2ce-08a3d89716d1"), // client id
    HttpUtility.UrlEncode(""), // scope (Web.Read, Site.Write, etc)
    HttpUtility.UrlEncode("http://localhost:44328/") // redirect uri
    ));
}

void webBrowser1_Navigating(object sender, NavigatingCancelEventArgs e)
{
  if (e.Uri.AbsoluteUri.StartsWith("http://localhost:44328"))
  {
    var queries = HttpUtility.ParseQueryString(e.Uri.Query);
    string authcode = queries["code"];
    this.GetAccessToken(authcode);
  }
}

// Step2 : Get access token from code
void GetAccessToken(string authcode)
{
  HttpClient cl = new HttpClient();
  var requestBody = new List<KeyValuePair<string, string>>();
  requestBody.Add(
    new KeyValuePair<string, string>("grant_type", "authorization_code"));
  requestBody.Add(
    new KeyValuePair<string, string>("client_id", "c8c0fa74-629e-47da-b2ce-08a3d89716d1@o365demo01.onmicrosoft.com"));
  requestBody.Add(
    new KeyValuePair<string, string>("client_secret", "fMeStxeorB..."));
  requestBody.Add(
    new KeyValuePair<string, string>("code", authcode));
  requestBody.Add(
    new KeyValuePair<string, string>("redirect_uri", "http://localhost:44328/"));
  var resource = string.Format(
    "00000003-0000-0ff1-ce00-000000000000/{0}.sharepoint.com@{1}",
    "o365demo01",
    "o365demo01.onmicrosoft.com");
  requestBody.Add(
    new KeyValuePair<string, string>("resource", resource));
  var httpContent = new FormUrlEncodedContent(requestBody);
  var res = cl.PostAsync(
    string.Format("https://accounts.accesscontrol.windows.net/{0}/tokens/OAuth/2", "o365demo01.onmicrosoft.com"),
    httpContent).Result;
  var jwtToken = res.Content.ReadAsStringAsync().Result;
  //MessageBox.Show(jwtToken);
  this.GetWebTitleFromSPO(jwtToken);
}

// Step3 : Call sharepoint online rest api using access token (get web title)
void GetWebTitleFromSPO(string tokenStr)
{
  JObject tokenObj = JObject.Parse(tokenStr);
  var token_type = tokenObj["token_type"].Value<string>();
  var access_token = tokenObj["access_token"].Value<string>();
  HttpClient cl = new HttpClient();
  var acceptHeader = new MediaTypeWithQualityHeaderValue("application/json");
  acceptHeader.Parameters.Add(
    new NameValueHeaderValue("odata", "verbose"));
  cl.DefaultRequestHeaders.Accept.Add(acceptHeader);
  cl.DefaultRequestHeaders.Authorization
    = new AuthenticationHeaderValue(token_type, access_token);
  var httpRes =
    cl.GetAsync("https://o365demo01.sharepoint.com/sites/test1/_api/web/title").Result;
  var resultStr = httpRes.Content.ReadAsStringAsync().Result;
  JObject resultObj = JObject.Parse(resultStr);

  MessageBox.Show(resultObj["d"]["Title"].Value<string>());
}
. . .

 

今回はネイティブ アプリを想定した内容ですが、Web アプリケーションの場合は、もちろん、Single-SIgnOn になるので (既に Office 365 に SignIn している場合、上記のログイン画面がスキップされます)、Web アプリケーション シナリオにおける server-to-server の相互連携でも活用していただけます。

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s