Azure App Service の Authentication 徹底解説


こんにちは。

Azure App Service に Built-in されている Authentication / Authorization は、簡単な認証・認可を実装したい場合、プログラム コードをいっさい変えずに対処できるため、通称「Easy Auth」とも呼ばれていて、この仕組みをより深く理解しておくと、さまざまな応用が可能になります。(例えば、Mobile App で使用している Backend にカスタム コードからアクセスする、など)

そこで本投稿では、この機能を 敢えて Easy でない領域まで徹底的に解説し、理解を深めたいと思います。(よくご質問もいただくので。。。)

なお、あまりこの仕組みに拘って、無理矢理、高度な使い方をする必要はありません。認証用のコードを直接組み込むなど、従来の手法でプログラミングしても 勿論 OK ですので、適材適所で活用してください。

補足 : 以前、「Azure Mobile Services の Client-directed Login」の投稿で考え方を紹介しましたが、この頃とかなり中身も変わりましたので、改めて本投稿でまとめます。

基本 (Basics)

Azure App Service の Authentication / Authorization は、Web App, Api App, Mobile App, Azure Functions など Azure App Service をベースとしたサービスで共通で使用されています。以降では、それぞれのアプリの形態を意識して、どのような利用方法が可能か、さまざまなシナリオを見ていきますが、サービスごとに機能がわかれているわけではなく、ベースは すべて共通です。

まずは、基本的な設定方法や動きを Web App (画面を持った Web アプリ) を例に紹介します。

Azure Portal で、作成した App Service (Web App, Api App) の [Settings] をクリックします。[Authentication / Authorization] (認証 / 認可) があるので、これをクリックします。
表示される下図の画面で、[App Service Authentication] を [On] にすると、Facebook, Google, Twitter, Microsoft Account, Azure Active Directory (Azure AD) の構成が可能になります。

設定方法はどれも同様で、Facebook, Google などの各 Authentication Provider (Identity Provider) で App (Client) の登録をおこない、そこで取得される Client Id (App Id) や Secret を この Azure App Service Authentication の構成画面 (上図) に設定すれば OK です。(すべての Provider について手順を解説していると冗長になってしまうので、ここではこの設定手順の詳細は説明しません。)

どの Provider (Facebook, Google, etc) も OpenID / OAuth をベースとした認証・認可になりますが、内部で、https://{your domain prefix}.azurewebsites.net/.auth/login/{provider}/callback (例 : https://appdemo01.azurewebsites.net/.auth/login/facebook/callback) のエンドポイントが使用されるので、Provider 側 (Facebook, Google, Microsoft Account, etc) にこの Url を Reply Url (Redirect Uri) として入れておいてください。

また、Azure Active Directory (Azure AD) の構成では Issuer Url を入力しますが、下図の通り、https://sts.windows.net/{tenant id}/ の Url を設定します。この Issuer Url は、特定の tenant のアカウントのみでログインできる点に注意してください。

構成が完了したら、下図のように、その Provider で Login するように設定して Save しましょう。(下図の [Log in with Facebook] を参照。)
下図では、Facebook を構成した場合の例です。

設定は以上で完了です。
プログラム コード (.NET, PHP, Java, Python, etc) のいっさいの変更は不要です。

Web Browser を使ってこの App Service (Web App) のページにアクセスすると、Redirect されて Provider のログイン画面が表示されます。

ID / パスワードを入力してログインすると、Redirect されて App Service (Web App) のページが表示されるはずです。

なお、後述しますが、いくつかの応用的な機能を使う際には、Token Store が必要ですので、このあと解説するいくつかの機能を使用するために、下図の通り、この Token Store の設定を [On] にしておいてください。(このあと解説します。)

補足 : この設定をおこなうと token が Server 上に保管されます。実体は、D:/home/data/.auth/tokens にあります。(Kudu Console などで確認してみてください。)

Token と Cookie

上述の処理の内部では専用の Cookie (AppServiceAuthSession cookie) が使用されています。(なお、Cookie-less の Flow も可能です ! このあと解説します。)
App Service は Provider (Facebook など) の認証に成功すると、Provider (Facebook など) 用の token を受け取り、内部で App Service にアクセスするための専用の token (access token) を生成して、この cookie の中にこうした情報を encrypt して設定しています。以降、App Service (Web App など) では、この cookie の内容を見て認証済が否かなどを判断しています。

このあと解説しますが、上述の [Token Store] を On にした場合は、Provider のログイン完了後に、下記の通り token が付与されて戻ってきます。

/.auth/login/done#token=%7B%22authenticationToken%22%3A...

この token の query string は、下記の Json を Url Encode した文字列であり、下記の authenticationToken が App Service 専用の access token です。このあと解説しますが、この access token を使って Api App などの呼び出しが可能であり、Mobile App SDK などでは、この方法で Backend の Api と連携しています。

{
  "authenticationToken": "eyJ0eXAiOi...",
  "user": {
    "userId": "sid:df003b9147..."
  }
}

選択的な保護 (Deferred Authentication)

上記の通り設定すると、サイト全体 (https://***.azurewebsites.net/ 全体) が保護されますが (認証済でない場合、サイト上のすべてのページで自動で Redirect されます)、一部のページなど選択的に保護する場合は、設定画面で、下図の通り [Allow request (no action)] を選択します。

このように設定すると、基本的にすべてのページにアクセスが可能になり、各ページで認証済か否かの判定を (プログラム コードなどを使って) おこなう必要があります。

ASP.NET の場合は、この設定は [Authorize] 属性で可能です。(細かなコードを記述しなくても、App Service 上の Provider によって自動で処理されます。)
例えば、ASP.NET MVC で /Home/Test の Uri のみを認証によって保護したい場合、下記太字の通り設定します。
認証されていない状態で /Home/Test にアクセスすると、HTTP 401 が返ります。(画面上には、認証エラーが表示されます。)

[Authorize]
public ActionResult Test()
{
  . . .

  return View();
}

なお、ASP.NET 以外 (PHP, Python, etc) の場合、ドキュメント (MSDN) に依れば、自力で cookie (上述の AppServiceAuthSession cookie) を使用するよう記載していますが、Decrypt などに関する情報 (Decrypt で使用する Purpose 等の情報) が提供されていないので、実質、この方法は不可能と思ってください。
認証がおこなわれると、Server Variables の REMOTE_USER にログイン ユーザーの名前が設定されるので、この値を使って判断するなどしてください。(なお、Claim 情報の取得方法については後述します。)
下記は、PHP を使用したサンプルです。

<?php
$authuser = $_SERVER['REMOTE_USER'];
. . .
?>

認証されていないユーザーにログイン ボタンを使ってログインを促す場合、/.auth/login/{provider} (例 : https://appdemo01.azurewebsites.net/.auth/login/facebook) に飛ばすことで、Provider のログイン画面が表示され、ログインに成功すると /.auth/login/done に戻ってきます。
もし、認証後に特定のページ (URL) にリダイレクトさせたい場合は、/.auth/login/{provider}?post_login_redirect_url={redirect url} に飛ばします。
例えば、下記は、ログイン完了後に /Home/Index に飛びます。

https://appdemo01.azurewebsites.net/.auth/login/facebook?post_login_redirect_url=%2FHome%2FIndex

Token の再取得 (refresh token の使用)

内部で token が使用されていることを上述しましたが、実は、この access token には有効期限があり、有効期限が切れた場合は、再度、token を取り直す必要があります。
そこで、Google, Microsoft Account, Azure AD のような refresh token をサポートする Provider を使用した場合、Azure App Service Authentication を使って App Service 用の token の再取得 (取り直し) ができるようになっています。(Facebook など refresh token をサポートしていない Provider の場合は、有効期限が切れた際に、上記のフローを繰り返して token を再取得してください。)

まず準備として、Google の場合は access_type=offline、Microsoft Account の場合は scope=wl.offline_access などを付与して、 Provider 向けの OAuth の処理で refresh token を取得するように構成します。
例えば、Microsoft Account の場合は、下図の通り、Azure App Service Authentication の設定画面で [wl.offline_access] を選択します。

補足 : Azure AD の場合は、こちら の手順に沿って Permission の付与や App Service Authentication の再構成をおこなってください。(少々手順が煩雑なので、ここでの解説はしません。)

あとは上述の通り、Azure App Service 用の token (access token) を取得し、この token が期限切れになったら、同じ Web Browser を使って下記の /.auth/refresh にアクセスすると、内部で access token の再取得をおこなって、下記の通り AppServiceAuthSession の cookie の再設定がおこなわれます。(上述の通り、この cookie 内部には、新しい token の情報が含まれています。)

HTTP Request

GET https://apisdemo01.azurewebsites.net/.auth/refresh
Cookie: AppServiceAuthSession=LHvOyMGXBf...

HTTP Response

HTTP/1.1 200 OK
Set-Cookie: AppServiceAuthSession=rnodJ7gdQl...; path=/; secure; HttpOnly

なお、上記では、再取得した access token が表示されていませんが (cookie の値の中に隠蔽されていますが)、Rest Api を使った access token の再取得方法については後述します。

Profile の取得

上述の通り、Cookie (AppServiceAuthSession) でログイン状態が保持されているため、同じブラウザーを使って下記の /.auth/me にアクセスすることで、現在ログインしているユーザーの基本情報 (Basic Profile) を取得できます。

HTTP Request

GET https://apisdemo01.azurewebsites.net/.auth/me
Cookie: AppServiceAuthSession=IiFbLt6jAa...

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json

[
  {
    "access_token": "EAAY94JYkQ...",
    "expires_on": "2016-08-21T03:40:01.6583447Z",
    "provider_name": "facebook",
    "user_claims": [
      {
        "typ": "http:\/\/schemas.xmlsoap.org\/ws\/2005\/05\/identity\/claims\/nameidentifier",
        "val": "1132990870..."
      },
      {
        "typ": "http:\/\/schemas.xmlsoap.org\/ws\/2005\/05\/identity\/claims\/name",
        "val": "Tsuyoshi Matsuzaki"
      },
      {
        "typ": "urn:facebook:link",
        "val": "https:\/\/www.facebook.com\/app_scoped_user_id\/1132990870091923\/"
      },
      {
        "typ": "http:\/\/schemas.xmlsoap.org\/ws\/2005\/05\/identity\/claims\/givenname",
        "val": "Tsuyoshi"
      },
      {
        "typ": "http:\/\/schemas.xmlsoap.org\/ws\/2005\/05\/identity\/claims\/surname",
        "val": "Matsuzaki"
      },
      {
        "typ": "http:\/\/schemas.xmlsoap.org\/ws\/2005\/05\/identity\/claims\/gender",
        "val": "male"
      },
      {
        "typ": "urn:facebook:locale",
        "val": "ja_JP"
      },
      {
        "typ": "urn:facebook:timezone",
        "val": "9"
      }
    ],
    "user_id": "Tsuyoshi Matsuzaki"
  }
]

同様に、この Basic Profile の Rest Api を使った取得方法は後述します。

Server Side での Claim 情報の取得

Azure App Service 上の Web App, Api App などの中からログインしているユーザー情報などを参照する場合、ASP.NET の場合は、普通に IPrincipal から Claim 情報を取得できます。(Azure App Service 上の Provider が、これらの設定をおこなっています。)
例えば、下記のサンプル コードでは、メールアドレス、氏名などの基本的な情報 (取得可能な Claim の一覧) が返されます。

[Authorize]
public ActionResult Test()
{
  ClaimsPrincipal principal = HttpContext.User as ClaimsPrincipal;
  string res = "";
  foreach (var claim in principal.Claims)
  {
    res += claim.Type + "=" + claim.Value + "<br>";
  }
  ViewBag.Message = res;

  return View();
}

また、Token Store が On の場合、下記の通り、Server 側で HTTP Header から元の Provider (Facebook, Google, etc) の情報が取得できるため、PHP, Java など ASP.NET 以外の Application から使用する場合は、この方法が使えます。
下記サンプルのように、認証した Provider 側 (今回の場合、Facebook) の access token も取得できるため、この token を使ってバックエンドから Provider (Facebook など) の API を呼び出すことも可能です。(例えば、「友達リストの取得」など。上述の通り、あらかじめ、適切な scope を追加しておいてください。)

<?php
  $headers = getallheaders();
  echo $headers['X-Ms-Token-Facebook-Access-Token'];
?>

補足 : Identity Provider によって下記の token が使用できます。(refresh token を取得する際の事前準備は、上述を参照してください。)
X-MS-TOKEN-FACEBOOK-ACCESS-TOKEN
X-MS-TOKEN-GOOGLE-ID-TOKEN
X-MS-TOKEN-GOOGLE-ACCESS-TOKEN
X-MS-TOKEN-GOOGLE-REFRESH-TOKEN
X-MS-TOKEN-MICROSOFTACCOUNT-ACCESS-TOKEN
X-MS-TOKEN-MICROSOFTACCOUNT-AUTHENTICATION-TOKEN
X-MS-TOKEN-MICROSOFTACCOUNT-REFRESH-TOKEN
X-MS-TOKEN-TWITTER-ACCESS-TOKEN
X-MS-TOKEN-AAD-ID-TOKEN
X-MS-TOKEN-AAD-ACCESS-TOKEN
X-MS-TOKEN-AAD-REFRESH-TOKEN

もちろん、ログインユーザーの Principal 名など、基本的な情報も、この X-MS- で始まる下記のヘッダーを使って参照できます。

Cookie-less Flow

さて、上記は、Web App のように Web Browser 上で動く画面を持つ Application を想定しましたが、Api App のように Client プログラムから REST などを使って呼び出す場合は、こうした cookie に頼った方法は使えません。
こうしたプログラムでは、これから紹介する方法で OAuth / REST を処理します。

Cookie-less Flow – Token の取得

このあと解説するように、ここで紹介するフローでは、Cookie の代わりに access token と呼ばれる文字列を使用します。

この access token を取得するには、上述の [Token Store] を On にします。/.auth/login/{provider} にアクセスすると Provider (Facebook など) のログイン画面が表示され、このログイン完了後に、下記の通り token が付与されて戻ってきます。

/.auth/login/done#token=%7B%22authenticationToken%22%3A...

この token の query string は、下記の Json を Url Encode した文字列です。この中の authenticationToken が、App Service 専用の access token になります。

{
  "authenticationToken": "eyJ0eXAiOi...",
  "user": {
    "userId": "sid:df003b9147..."
  }
}

この eyJ0eXAiOi… の文字列は、Identity 業界に居る方なら萌える方も多いと思います。
そうです、「Azure AD を使った API 開発 (access token の verify)」で解説した JWT Token で、この JWT Token を解析することで、token の正しさの確認 (verify) や、Claim 情報の取得などが可能です。

なお、この JWT Token を Decode した場合、下記のような Claim が入っています。(下記は Facebook の場合の例です。)

{
  "stable_sid": "sid:1ead5da614...",
  "sub": "sid:df003b9147...",
  "idp": "facebook",
  "ver": "3",
  "iss": "https://appdemo01.azurewebsites.net/",
  "aud": "https://appdemo01.azurewebsites.net/",
  "exp": 1471744628,
  "nbf": 1466560825
}

Cookie-less Flow – Client-directed Login

上記の方法以外に、Client-directed Login と呼ばれる方法を使って、Facebook SDK など Provider が提供する既存の SDK と組み合わせて access token (App Service 専用の access token) を取得できます。(また、AngularJS など、Front-end 主体のアプリケーションの場合にも、ここで紹介する方法が使えます。)

まず、普通に、各 Provider (Facebook, Google, Twitter, etc) にアクセスして、Provider の access token を取得します。この方法の詳細は、Google, Facebook, Live Services などの各 API のドキュメントを参照してください。
例えば、Microsoft Account の場合は、「Live Services (Outlook.com, OneDrive, etc) 開発」で解説したように、Web Browser (または Web Browser の Component) のアドレス欄に下記を入力して、Microsoft Account のログイン画面を表示します。(使用する client id など、事前準備の詳細は「Live Services (Outlook.com, OneDrive, etc) 開発」を参照してください。)

https://login.live.com/oauth20_authorize.srf
  ?response_type=code
  &client_id=000000004019ED2C
  &scope=wl.signin%20wl.offline_access
  &redirect_uri=https%3A%2F%2Fapisdemo01.azurewebsites.net

Web Browser 上でログインが完了すると、Redirect されて下記の通り、code を付与して戻ります。

https://apisdemo01.azurewebsites.net/?code=Mb2900d43...

アプリケーション側で この戻ってきた code を取得し、この code を使って、下記の通り、HTTP POST を Request すると、Response として、下記の通り、Provider の access token (今回の場合、Microsoft Account の access token) が戻ってきます。

HTTP Request

POST https://login.live.com/oauth20_token.srf
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=Mb2900d43...&client_id=000000004019ED2C&client_secret=P9BjaLvubm...&redirect_uri=https%3A%2F%2Fapisdemo01.azurewebsites.net

HTTP Response

{
  "token_type": "bearer",
  "expires_in": 3600,
  "scope": "wl.signin wl.offline_access",
  "access_token": "EwBwAq1DBA...",
  "refresh_token": "MCXbHXp0i5...",
  "authentication_token": "eyJ0eXAiOi...",
  "user_id": "112c1387ec..."
}

Provider (今回は Microsoft Account) の access token が準備できたら、下記の通り、この Provider 用の access token を使って、App Service 専用の access token が取得できます。(この access token は、上述で取得したものと同じ、App Service 専用の access token です。)

補足 : Google など OpenID Connect が使用できる場合は、codeid_token を使って、下記の通り Request しても構いません。
{"authorization_code":"<received code>", "id_token":"<received id_token>"}

HTTP Request

POST https://apisdemo01.azurewebsites.net/.auth/login/microsoftaccount

{"access_token":"EwBwAq1DBA..."}

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "authenticationToken": "eyJ0eXAiOi...",
  "user": {
    "userId": "sid:3302da51c2..."
  }
}

Cookie-less Flow – Api App の呼び出し

上述の通り、/.auth/login/{provider} で App Service 用の access token を取得したら、その access token を X-ZUMO-AUTH ヘッダーに設定することで、App Service で保護された Api App の呼び出しが可能です。(Cookie は必要ありません。)
例えば、下記は、Azure App Service Authentication で保護された Api App を呼び出すサンプルです。

GET https://apisdemo01.azurewebsites.net/api/values/5
X-ZUMO-AUTH: eyJ0eXAiOi...

Cookie-less Flow – Profile の取得

また、/.auth/me に対しても、X-ZUMO-AUTH ヘッダーを使って、上記同様に Basic Profile を取得できます。(上記では cookie を使っていましたが、下記では、access token を使って Basic Profile を取得します。)

HTTP Request

GET https://apisdemo01.azurewebsites.net/.auth/me
X-ZUMO-AUTH: eyJ0eXAiOi...

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json

[
  {
    "access_token": "EwBwAq1DBA...",
    "authentication_token": "eyJ0eXAiOi...",
    "expires_on": "2016-06-22T05:29:44.9643501Z",
    "provider_name": "microsoftaccount",
    "refresh_token": "MCRqh8*3K9...",
    "user_claims": [
      {
        "typ": "http:\/\/schemas.xmlsoap.org\/ws\/2005\/05\/identity\/claims\/nameidentifier",
        "val": "9c0af81b73..."
      },
      {
        "typ": "http:\/\/schemas.xmlsoap.org\/ws\/2005\/05\/identity\/claims\/name",
        "val": "Tsuyoshi Matsuzaki"
      },
      {
        "typ": "http:\/\/schemas.xmlsoap.org\/ws\/2005\/05\/identity\/claims\/givenname",
        "val": "Tsuyoshi"
      },
      {
        "typ": "http:\/\/schemas.xmlsoap.org\/ws\/2005\/05\/identity\/claims\/surname",
        "val": "Matsuzaki"
      },
      {
        "typ": "urn:microsoftaccount:locale",
        "val": "ja_JP"
      },
      {
        "typ": "urn:microsoftaccount:id",
        "val": "9c0af81b73..."
      },
      {
        "typ": "urn:microsoftaccount:name",
        "val": "Tsuyoshi Matsuzaki"
      }
    ],
    "user_id": "Tsuyoshi Matsuzaki"
  }
]

Cookie-less Flow – Token の再取得 (refresh token の使用)

access token が期限切れになった場合も、X-ZUMO-AUTH ヘッダーを使って、上記同様に /.auth/refresh に HTTP POST することで、access token (App Service 専用の token) の再取得が可能です。(refresh token を使用する場合の事前準備については、上述を参照してください。)

HTTP Request

POST https://apisdemo01.azurewebsites.net/.auth/refresh
X-ZUMO-AUTH: eyJ0eXAiOi...

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "authenticationToken": "eyJ0eXAiOi...",
  "user": {
    "userId": "sid:3302da51c2..."
  }
}

Azure AD Authentication による Api App

上記では X-ZUMO-AUTH という Azure App Service 独自の HTTP Header を使いましたが、実は、Provider が Azure AD の場合のみ、「Azure AD を使った API 連携の Client 開発 (OAuth 2.0)」で解説した手法 (つまり、Provider による普通のフロー) がそのまま使えます。(これは嬉しいですね !)

基本的な利用方法は、「Azure AD を使った API 連携の Client 開発 (OAuth 2.0)」で解説した手法に沿って、Api 側 (Server Side) と Client 側 (Client Side) を Azure AD に登録して、Api 側 (Server Side) の Client Id を Azure App Service Authentication に設定し、あとは普通の OAuth のフローで Client Side から Server Side (Azure App Service) を利用します。

具体的な手順を以下に記述します。

  1. Azure Portal を使って、Azure AD に Server Side の Application を登録します。
    この際、Reply Url には、Azure App Service の Url (および、/.auth/login/aad/callback) を登録します。
  2. Azure AD に Client Side の Application を登録します。(Client 側の Reply Url などは、皆さんのアプリケーションにあわせ、自由に設定して構いません。)
    この際、Client Side から Server Side を呼び出すための Permission を設定します。(下図)
  3. Azure App Service の Authentication として、上記で登録した Server Side の Application の Client Id を設定します。(下図)
  4. Azure AD を使った API 連携の Client 開発 (OAuth 2.0)」で解説した手順で、Client Side の Application を構築して access token を取得します。なお、この際、resource として Server Side の Application を指定します。
    access token を取得したら、「Azure AD を使った API 連携の Client 開発 (OAuth 2.0)」で解説した手順で、Azure App Service の Api App に対して Authorization Header を設定して呼び出します。(上述の X-ZUMO-AUTH の Header は不要です。)

この方法では、「Azure AD : Backend Server-Side アプリの開発 (Daemon など)」で取得した app-only の access token も使用できるので、特に、画面 (UI) を持たない Server アプリケーション (Daemon など) から Azure App Service Authentication にバックエンドでアクセスする場合には有効な手法です。

 

Comments (0)