連載:Microsoft技術におけるアイデンティティ連携開発のいま(3)

連載:Microsoft技術におけるアイデンティティ連携開発のいま(3)

カスタム・アプリケーションによる認証フローとプログラミング

2013年8月12日

ネイティブ・アプリのプログラム・コードから認証を行う方法とは? Azure Active Directoryを例に、OAuth 2.0をベースにした、プラットフォームに依存しない新しい手法を考察する。

日本マイクロソフト 松崎 剛
  • このエントリーをはてなブックマークに追加

プログラマーにとっての新たな課題

 ユーザー(=利用者)にとって便利なことは、必ずしも開発者(=プログラマー)にとって便利であるとは限らない。

 これまでの連載の中で見てきたように、Google/Facebook/Microsoftアカウントなど、昨今のクレーム・ベースの認証は、やりとりに関するルールが「Webブラウザーを使用してアクセスすること」を前提として標準化されている(つまり、Web標準をベースとして、標準化されている)。しかし、標準化されているのは、多くがHTTPのリクエスト(Request)と、それに対するレスポンス(Response)であり、その先の内部実装は、HTTPヘッダー、Cookie、JavaScriptなどの多くの手法が使用され、プロバイダーに依存した実装となっている。

 このため、Webブラウザーそのものを使用せず、こうした認証の処理(=手続き)をプログラム・コードで処理しようとすると、従来の基本認証(=Basic認証)などと異なり、面倒なことになるのだ。「Webブラウザーが実施していることをそのままプログラム・コードで実現すればよい」と考えるかもしれないが、HTTPヘッダー、Cookie、JavaScriptなど、あらゆる可能性を考えてプログラミングすることは、事実上、「Webブラウザーそのものを作ること」に等しいからだ。また、たとえ特定のプロバイダーだけで動作するような限定的な作り方をしたとしても、そのプロバイダーが内部の実装方法を変えた際にプログラムは動かなくなってしまうだろう(内部実装については、約束されていないためだ)。また、HttpOnly属性のCookieのように、外部のプログラム・コードからは操作できないような手法が使用されているケースもある。

 しかし、こうした「プログラム・コードから認証を行う」というニーズは、今でも多く存在する。例えば、スマートフォンのネイティブ・アプリから使用する場合だ。あるいは、複数のサービス(サーバー)同士がバックエンドで連携するケースも考えられるだろう。クレーム・ベース認証の時代に入り、プログラマーにとって少々厄介な課題が生じてきたようだ。

 こうした状況を背景に、いくつかのプロバイダー(もしくは、認証に関する標準仕様)では、こうしたプログラム・コードからアクセスするための方法を提供している。ここでは詳細を解説しないが、WS-Trustや、OAuthのImplicit Grant、SAMLのECP(Enhanced Client or Proxy)などは、こうした手法の1つだろう。今回の第3回では、前回紹介したMicrosoft Azure(旧称:Windows Azure) Active Directoryを例に、開発者にとって重要な、こうした手法について考察したい。

これまでの歴史

 第2回でも紹介したように、最近のMicrosoftの認証基盤は、よりオープンで、よりシンプルな方式を採用する傾向にあるが、まずは、これまでの歴史で、Microsoftがどのような解決方法を提供してきたか軽く解説しておこう(従来の手法も、今なお使用可能であり、読者には、手法を選択するうえで、その思想の違いを理解してほしいためだ)。

 第2回で紹介したように、これまでMicrosoftのアイデンティティ・プラットフォームはWS-*(WS-Federationなど)をベースとしていた。このため、こうしたカスタムのログインや、ユーザー・インターフェイス(UI、画面)を持たないバックエンドのログインを行う場合に備え、WS-Trustを使用できるようになっていた。

 実際、Windows Server Active Directory(以降、Active Directory)や、第2回で解説したAccess Control(以降、ACS)、Office 365などでは、今なおWS-Trustのエンドポイントを提供しており、このエンドポイントを呼び出すことでプログラミングのみでログイン処理を実行し、認証結果として必要なトークンを取得することが可能だ。こうしたプログラミングを行うには、第2回で解説した「Windows Identity Foundation(WIF)」を使用する(このWS-Trustによるプログラミング手法は、筆者のブログ「AD FSフェデレーションを処理するClient Programming」を参照していただきたい)。しかし、WS-Trustは、決して簡単なプロトコルではなく、ライブラリを使用せずに自力で処理するのは困難だと思ってよいだろう。なお、(WS-Trustを使用した)この方法では、ログイン画面も、独自なUIを作成して提供することが可能だ。

 そして近年、ソーシャル・アイデンティティを中心としたクレーム・ベース認証(=Facebookアカウント、Googleアカウント、MicrosoftアカウントなどのWS-*を使用しない方式)が主流になってきた。そこで、マイクロソフトが提供するアイデンティティ基盤のいくつかでも、カスタム認証を行う際に、Webブラウザーと連携する手法が一般的に使用されるようになる(例えば、次回解説する多要素認証と組み合わせている場合など、プロバイダーが提供するログイン画面の上では多くの「仕事」が行われている。これらを全てプログラム側で制御するのは、もはや非現実的と考えてよいだろう)。ネイティブ・アプリ(=Windows、iOS、Androidなどの上で動くアプリケーション)では、ほとんどの場合、Webブラウザー・コンポーネントに相当するオブジェクトが使えるが、認証における複雑な処理をこうしたWebブラウザー・コンポーネントに任せて、結果の値(例えば、認証結果のトークンなど)だけを取得するという手法だ。

 例えば、第2回で紹介したAzure Active DirectoryのACSでは、ブラウザー外連携の技術を使って、こうしたプログラミングが可能だ。ACSにアクセスする際、URLのクエリー文字列として「protocol=javascriptnotify」を指定すると、認証完了後に下記のようなJavaScriptのコードを返すようになっている。

JavaScript
<script type="text/javascript">
try{
  window.external.notify('{"appliesTo":"http://localhost/testapp1","context":null,"created":1373427938,"expires":1373428538,"securityToken":"eyJ0eXAi...","tokenType":"urn:ietf:params:oauth:token-type:jwt"}');
}
catch(err){
  alert("Error ACS50021: window.external.notify is not registered.");
}
</script>
ブラウザー外連携のJavaScriptコード

 ネイティブ・アプリがPC上のデスクトップ・アプリやSilverlightアプリの場合、上記の「window.external.notify」関数によって、ブラウザー・コンポーネントをホストしているネイティブ・アプリにデータが通知される(ネイティブ・アプリ側では、このイベントを処理する)。送受信される情報のフォーマットも、WS-Trustと比べて非常にシンプルなものとなっている。

最新のネイティブ・アプリケーション連携手法

 さて、多くのプログラマーは上記のACSの手法に欠点があることにお気づきだろう。このブラウザー外連携の手法はInternet Explorerで使用可能だが、そのほかのプラットフォームでは通用しないためだ。例えば、iPhoneアプリでUIWebViewクラスを使って、これと同じ処理をプログラミングしようと思っても不可能だ。

 実は最新のAzure Active Directoryでは、(ブラウザー外連携ではなく)OAuth 2.0をベースに、プラットフォームに依存しない新しい方法が扱えるようになっており、SharePoint OnlineやActive Directory Federation Services(以降、AD FS)など、今後のマイクロソフトの多くのプラットフォームで、このOAuth 2.0ベースの方式による連携が主流となってくる予定だ*1

 以下で、簡単に、その処理の流れを見てみよう。

  • *1最新のWindows Server 2012 R2においても、Active Directory(正確にはAD FS)でOAuthがサポートされる。詳しくは「Active Directory Team Blog(英語)」を参照していただきたい。

 例えば、Azure Active Directoryの「o365demo01.onmicrosoft.com」のテナントに登録されている「testapp1」(URLは「http://localhost/testapp1」)から、同じテナントに登録されている「testapp2」(URLは「http://localhost/testapp2」)のAPI(REST APIなど)を呼び出す場合を想定しよう。この場合、まず、Webブラウザー・コンポーネントなどを使い、下記のようなURLにアクセスする(事前準備と、下記のclient_idの取得方法については、ここでは解説を省略する。筆者のブログ「Azure Active DirectoryのLoginと連携したNative Application開発」を参照してほしい)。

HTTP
GET https://login.windows.net/o365demo01.onmicrosoft.com/oauth2/authorize?response_type=code&resource=testapp2/localhost&client_id=5bbe4005-5e99-4a51-9846-f0dd9a02549a&redirect_uri=http://localhost/testapp1
認可用コード(Authorization Code)を取得するHTTPリクエストのサンプル

 上記のURIにアクセスすると、次に示すログイン画面が表示される(厳密にはログイン画面にリダイレクトされる)。

ログイン画面(=サインイン画面)の表示

 ログインが完了すると、上記のURIで指定された「redirect_uri」クエリー・パラメーター(今回の場合、「http://localhost/testapp1」)にブラウザーがリダイレクトを行い、そこでログインしたユーザーのUser Contextを含む認可用コード(Authorization Code。以降、code)がクエリー文字列として渡される。すなわち、以下のようなURLにリダイレクトされるわけだ。

http://localhost/testapp1?code=AgAAAAAAjYxaWEmhi_qYPxQYHmw……省略……
リダイレクトされたURIのサンプル

 ネイティブ・アプリでは、このナビゲーション(リダイレクト)のイベントを処理して、クエリー文字列に含まれるcodeを取得する。

 次に、取得したcodeからOAuthのAccess Token(=アクセス用トークン)を取得する。この取得には、以下のとおりHTTPのPOSTメソッドを呼び出せばよい。

HTTP
POST https://login.windows.net/o365demo01.onmicrosoft.com/oauth2/token
 
grant_type=authorization_code&code=AgAAAAAAjYxaWEmhi_qYPxQYHmw...&client_id=5bbe4005-5e99-4a51-9846-f0dd9a02549a&redirect_uri=http%3A%2F%2Flocalhost%2Ftestapp1
Access Tokenを取得するHTTPリクエストのサンプル

 上記のリクエストによって、下記のとおりJSONフォーマットのレスポンスを取得できる。なお、Access Tokenが期限切れになった場合は、下記の「refresh_token」(=Refresh Token。更新用トークン)からAccess Tokenを取り直すこともできる(ここでは解説を省略するが、この場合も、同様にHTTPを使って容易に取得可能だ)。

JSON
{
  "access_token":"eyJ0eXAiOiJKV1……省略……",
  "token_type":"Bearer",
  "expires_in":"28800",
  "expires_on":"1373631999",
  "multi_resource":true,
  "refresh_token":"AgAAAAAAZQxwv……省略……",
  "scope":"user_impersonation"
}
返されるレスポンスBodyのサンプル(JSONフォーマット)

【補足】Access TokenのUserコンテキスト

 Access Tokenには、Userコンテキストを含むトークンと、含まないトークンがあるが、この方法で取得されるAccess Tokenは、Userコンテキストを含んだトークンになる(Userコンテキストを含まないAccess Tokenの取得方法については、説明を割愛する)。

 以上で、サービス(http://localhost/testapp2)の呼び出しに必要なセキュリティ情報(Access token)の取得は完了だ。

 あとは、このトークンをヘッダーに設定して、サービスを呼び出せばよい(次のコードはその例。この方法で、Azure Active DirectoryのGraph APIなども同様に呼び出せる)。

HTTP
GET http://localhost/testapp2/testapi
 
Authorization: Bearer eyJ0eXAiOiJKV1……省略……
Access Tokenを使ったサービス呼び出しのHTTPサンプル

 見ていただいてお分かりのとおり、この方式では、前述したブラウザー外連携の方法とは異なり、必要な情報がURIの一部やHTTPのレスポンスとして素直に返ってくるため、iPhone(iOS)、Androidなどのネイティブ・アプリでも問題なく使用できる(なお、第2回で紹介したように、Windowsアプリの場合はActive Directory Authentication Library(ADAL)を使うことで、このフローが数行で実装可能だ)。

 また、ASP.NET、PHP、Node.jsなどのWebアプリのサーバーサイドから、Azure Active Directoryで保護されたほかのサービスを呼び出す場合(server-to-server interactions)でも、この手法が使用できる。それに加えて、上述のとおりブラウザーによる標準的なログイン方法を使用しているため、一度、Azure Active DirectoryにログインしたWebブラウザーの場合、ユーザーが再度、IDやパスワードを入力する必要はない。つまり、シングル・サインオン(SSO)も同時に満たすことが可能だ(OAuthを使ったこうした連携シナリオは、グーグルの開発プラットフォームでも同様に採用されている)。

 またここでは、ログイン画面を表示して、ユーザー・コンテキストでログインを行っているが、信頼されたサーバー同士の接続の場合(=ユーザー・コンテキストを使わないアプリ同士の連携の場合)には、例えば、Symmetric Key Credential(=対称キーCredential)を使ってバックエンドで連携することも可能だ(そのプログラミングのサンプルについては、筆者のブログ「Windows Azure Active DirectoryのGraph APIの活用」に記載したので参考にしてほしい)。

【注意】SharePoint Onlineの場合

 なお、SharePoint Onlineでは、独自の仕組みが提供されているため注意してほしい(基本的なフローは上記のコードと同じであるが、Permission(=アクセス許可)構成の方法や、使用するエンドポイントなどが異なっている)。SharePoint Onlineの場合の具体的な接続方法(=フロー)については、筆者のブログ「Native ApplicationでSharePoint OnlineにLoginしてRESTサービスなどを呼び出すプログラミング」に記載したので、是非参考にしてほしい。

 なお、上記のコードはサンプルとして記述しているが、実際の開発では配慮すべきポイントがいくつかあるので注意してほしい。例えば、URIの一部として渡されるセキュリティ情報(上記のcode)は、https(SSL)による暗号化などの保護は受けない。このため、上記のようにredirect_uriとして「http://localhost/testapp1」と指定すると、Webブラウザー・コンポーネントがこのサイトに接続しようとして、ネットワーク上を保護されないcodeが無意味に流れることになるだろう(このため、アプリによっては、http(s)以外のschemaを使うことも検討してほしい。例えばWindows 8ストア・アプリの場合は、「ms-app://...」を使用すべきだろう)*2

 オープンな方式(=APIなどに隠ぺいされていない方式)である代わりに、こうした配慮もプログラマー自身の責任として対処しなければならないので注意してほしい。

  • *2 OAuth 2.0のImplicit Grantのフローでは、URIの「クエリー文字列」(?、&)ではなく「アンカー」(#)を使用しているため、JavaScriptコードなどからの使用に際してセキュリティ情報をサーバーに送信しないようになっている(一方、上記のフローでは、クエリー文字列が使われているので事情が異なる)。

サービス側のプログラミング

 当然だが、上記の方法で呼び出されるサービス側も、Azure Active Directoryを使って、渡されたトークンの妥当性を検証する必要がある(Graph APIやSharePoint OnlineのRESTサービスも、もちろん、内部でトークンの検証を行っている)。

 前述のAuthorizationヘッダーで渡された文字列はエンコードされた文字列であり、筆者のブログ「OAuth 2 Token(JWT)のDecode」で示されているように、デコードして内容を取り出すことができる。

 トークンの妥当性を検証する際のフローは、サーバーに登録されているメタデータから証明書やIssuerの情報を取得し(例えば「o365demo01.onmicrosoft.com」のテナントの場合、「https://login.windows.net/o365demo01.onmicrosoft.com/federationmetadata/2007-06/federationmetadata.xml」のアドレスから取得できる)、上記のヘッダーで渡された情報と照合して妥当性を検証することになる。このプログラミングは、「JSON Web Tokenの仕様」に従う必要がある。また、ハッシュを使った証明書(Signature)のバイト列検証なども必要で、決して簡単なプログラミングとは言えないだろう。

 プログラミング言語によって、こうしたJWT(JSON Web Tokens)関連の処理が可能なライブラリーはいくつか存在するが、ASP.NETの場合には、第2回でも説明した「JSON Web Token Handler For the Microsoft .Net Framework 4.5」を使用するとよいだろう。このライブラリーを使用すると、JwtSecurityTokenHandlerクラスのValidateTokenメソッドで、必要な検証とPrincipalの取得を全て行ってくれる(このため、その後のユーザー情報の取得やカスタムの認可処理なども容易にプログラミング可能だ)。

 また、Visual Studio 2013のプロジェクト・テンプレートでも積極的に使用されているOWIN(Open Web Interface for .NET)を使用すると、こうしたプログラミングが、ASP.NETのフレームワークとシームレスに組み合わせて使用できるようになっている。

 例えばVisual StudioでASP.NET Web APIのプロジェクトを作成する場合、NuGetを使って「Microsoft.Owin.Host.SystemWeb」と「Microsoft.Owin.Security.WindowsAzure」のパッケージをインストールし、以下のStartup.csファイルのクラスを新規作成するだけでよい(ただし、本執筆時点では、これらのパッケージは、まだNightly Build版なので注意していただきたい。今後、クラス名やメソッドなども変更される可能性がある)。

C#
……省略……
using Owin;
using Microsoft.Owin.Security.WindowsAzure;
 
namespace testapp2
{
  public class Startup
  {
    public void Configuration(IAppBuilder app)
    {
      app.UseWindowsAzureBearerToken(
        new WindowsAzureJwtBearerAuthenticationOptions()
      {
        Audience = "testapp2/localhost",
        Tenant = "o365demo01.onmicrosoft.com"
      });
    }
  }
}
OWINを使用したJWTの検証設定

 あとは、通常のASP.NET Web APIの開発と同じように、保護したいAPI(メソッド)にAuthorize属性を追加するだけだ(下記のコードはその例だ)。

C#
public class ValuesController : ApiController
{
  [Authorize]
  public IEnumerable<string> Get()
  {
    return new string[] { "value1", "value2" };
  }
  ……省略……
}
ASP.NET Web APIの保護

 前述したAuthorizationヘッダーとして正しい値が設定されている場合のみ、このWeb APIの呼び出しは成功する(正しいトークンが設定されていない場合、HTTPステータスは「401(Unauthorized)」となって許可されない)。

 もちろんOWINを使用した場合も、JSON Web Token Handler(前述)の場合と同様に、Principalを取得してクレームを使用したプログラミングが可能だ(カスタム認可のプログラミングも容易に実現できる)。

最後に

 今回は、ネイティブ・アプリとの連携手法を中心に解説した。過去の歴史的経緯もあり、今なお、いくつかの手法が存在しているが、全体の方向性を理解していただけたと思う。

 さて今回は、Azure Active Directoryのみを対象にネイティブ・アプリ連携の手法を見てきたが、ネイティブ・アプリにおけるGoogle、Facebookなどのソーシャル・アイデンティティも含めた連携開発については、「Azureモバイル・サービス(Mobile Services)」と呼ばれる最新のプラットフォームを使うことが可能だ。

 次回は、本連載の最終回として、このAzureモバイル・サービスによる認証や、二要素認証など、最近の新しい基盤について、それらが解決しようとする課題やアーキテクチャを開発者の視点で見ていきたい。

1. .NETで使えるアイデンティティ連携のためのライブラリまとめ(前編)

Webアプリなどで、Facebook、Google、Twitter、企業内のActive Directoryなどの各アイデンティティ基盤と連携するための各技術(ライブラリやサービス)を、開発者視点で整理する連載スタート。

2. .NETで使えるアイデンティティ連携のためのライブラリまとめ(後編)

アイデンティティ連携で使用可能なライブラリやサービスなどをまとめる記事の後編。今回はAzure Active Directoryのアイデンティティ連携を中心に解説。

3. 【現在、表示中】≫ カスタム・アプリケーションによる認証フローとプログラミング

ネイティブ・アプリのプログラム・コードから認証を行う方法とは? Azure Active Directoryを例に、OAuth 2.0をベースにした、プラットフォームに依存しない新しい手法を考察する。

4. Azureの認証におけるその他サービス ~ Azureモバイルサービス、多要素認証

最終回。Azureモバイルサービスや、多要素認証など、これまでの連載で取り上げなかった、認証に関わる先進的な技術を簡単に紹介する。

サイトからのお知らせ

Twitterでつぶやこう!


Build Insider賛同企業・団体

Build Insiderは、以下の企業・団体の支援を受けて活動しています(募集概要)。

ゴールドレベル

  • グレープシティ株式会社
  • 日本マイクロソフト株式会社