しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

Easy Auth を有効にした Azure Functions と ClaimsPrincipal バインディングの挙動を確認した

API Management を使えば Azure AD を使った認証をサクッと有効化出来ますが、もっとライトに組み込みたいと思ったので Azure Functions の機能を使って同じように実現できるかを調べました。

当然ながら Easy Auth は App Service の機能なので、Azure Functions に限定した内容ではないです。

Easy Auth はいろんなプロバイダが使えますが、基本的には Azure AD を使った認証を使って試しました。Managed Identity や Microsoft Graph と組み合わせたり、地味に使いどころが多いのが Azure AD です。

f:id:shiba-yan:20191028001037p:plain

Windows App Service の Easy Auth は IIS Module として実装されているので、アプリケーションにトラフィックが届く前に色々処理が挟まります。

特に未ログイン状態の時の挙動が変わるので、これを機に気になる部分を試しました。

Easy Auth 有効後の未ログイン時レスポンス

Web API の場合は未ログイン時にログインページへリダイレクトされたら困るので、適当な API を作りいろんなパターンでリクエストを投げて確認して結果をまとめました。

  • UA 関係なく X-Requested-With = XMLHttpRequest の場合
    • 403 Forbidden を返す
  • UA がブラウザかつ X-Requested-With = XMLHttpRequest 以外の場合
    • リダイレクト用の HTML が返される (200 OK)
  • それ以外の場合(例 : UA / X-Requested-With がない)
    • 401 Unauthorized を返す

ちょっとわかりにくいですが、Web API として呼ばれたであろう時は 401 か 403 を返します。普通に XHR でブラウザからリクエストを投げた場合は 403 が返ってくるので分かりやすいです。

ブラウザかどうかの判定は Mozilla/ が UA の先頭にあるかを見てるだけっぽいです。結構ザル判定なので、User-Agent を HttpClient で指定する時には気を付けたい感じです。

あとフローによっては 401 と 403 と別々のステータスコードが返ってくるのに注意したいです。エラーハンドリングをステータスコードで見ている場合、素通りしてしまうこともありそうです。

ClaimPrincipal を使って認証状態を確認する

Easy Auth は認証周りを全部 IIS Module が行ってくれるので、実際にアプリから利用する場合は Claims を復元する必要がありますが、Azure Functions は自動で復元して HttpContext.User かバインディングで ClaimsPrincipal を渡してくれます。

以下のような Function を用意して、どのような Claims が渡されるか確認しました。Easy Auth は Azure AD を有効化しているので、他のプロバイダよりも Claims が多いです。

public static class Function1
{
    [FunctionName("Function1")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
        ILogger log)
    {
        var claimsPrincipal = req.HttpContext.User;

        var builder = new StringBuilder();

        builder.AppendLine($"Name = {claimsPrincipal.Identity.Name ?? "(null)"}");
        builder.AppendLine($"IsAuthenticated = {claimsPrincipal.Identity.IsAuthenticated}");
        builder.AppendLine($"AuthenticationType = {claimsPrincipal.Identity.AuthenticationType ?? "(null)"}");

        foreach (var claim in claimsPrincipal.Claims)
        {
            builder.AppendLine($"{claim.Type} = {claim.Value}");
        }

        return new OkObjectResult(builder.ToString());
    }
}

今回試して初めて知りましたが、Easy Auth 以外に Function Key や Host Key を使った認証の場合でも ClaimsPrincipal はセットされるようでした。

なので HttpContext.User を参照すれば認証の種類に関係なく、簡単にログイン済みか判別できます。

キー・トークン未指定
Name = (null)
IsAuthenticated = False
AuthenticationType = (null)
Function Key
Name = (null)
IsAuthenticated = True
AuthenticationType = WebJobsAuthLevel
http://schemas.microsoft.com/2017/07/functions/claims/authlevel = Function
http://schemas.microsoft.com/2017/07/functions/claims/keyid = default
Host Key (default)
Name = (null)
IsAuthenticated = True
AuthenticationType = WebJobsAuthLevel
http://schemas.microsoft.com/2017/07/functions/claims/authlevel = Function
http://schemas.microsoft.com/2017/07/functions/claims/keyid = default
Host Key (_master)
Name = (null)
IsAuthenticated = True
AuthenticationType = WebJobsAuthLevel
http://schemas.microsoft.com/2017/07/functions/claims/authlevel = Admin
http://schemas.microsoft.com/2017/07/functions/claims/keyid = master
Easy Auth (Azure AD / MSA)
Name = me@shibayan.jp
IsAuthenticated = True
AuthenticationType = aad
aud = 00000000-0000-0000-0000-000000000000
iss = https://sts.windows.net/00000000-0000-0000-0000-000000000000/
iat = 1572192363
nbf = 1572192363
exp = 1572196263
aio = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
http://schemas.microsoft.com/claims/authnmethodsreferences = pwd
c_hash = _CV4pcEcUNZnJPI3eAsdmg
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress = me@shibayan.jp
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname = Shibamura
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname = Tatsuro
http://schemas.microsoft.com/identity/claims/identityprovider = live.com
ipaddr = xxx.xxx.xxx.xxx
name = shibayan
nonce = 58b2598f76a84491ab5931c862f7fe4c_20191027161602
http://schemas.microsoft.com/identity/claims/objectidentifier = 00000000-0000-0000-0000-000000000000
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier = XXXXXXXXX-XXXXXXXXXXXXXXXXXXXXX_XXXXXXXXXXX
http://schemas.microsoft.com/identity/claims/tenantid = 00000000-0000-0000-0000-000000000000
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name = live.com#me@shibayan.jp
uti = hII4-LTwnkqDubPwk0-sAA
ver = 1.0
wids = 00000000-0000-0000-0000-000000000000
Easy Auth (Azure AD / Bearer Token)
Name = 00000000-0000-0000-0000-000000000000
IsAuthenticated = True
AuthenticationType = aad
aud = https://easyauth-api.azurewebsites.net
iss = https://sts.windows.net/00000000-0000-0000-0000-000000000000/
iat = 1572157734
nbf = 1572157734
exp = 1572186834
aio = 42VgYFB78dtXa23ZvvmV0dNO3y3wAQA=
appid = 00000000-0000-0000-0000-000000000000
appidacr = 2
http://schemas.microsoft.com/identity/claims/identityprovider = https://sts.windows.net/00000000-0000-0000-0000-000000000000/
http://schemas.microsoft.com/identity/claims/objectidentifier = 00000000-0000-0000-0000-000000000000
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier = 00000000-0000-0000-0000-000000000000
http://schemas.microsoft.com/identity/claims/tenantid = 00000000-0000-0000-0000-000000000000
uti = o151aNRqEUuy5TiJ9xBiAA
ver = 1.0

最後に書いてある Easy Auth (Azure AD / Bearer Token) というのはちょっと特殊な使い方です。微妙に Undocumented な使い方なので、いろいろと調べた後にブログにまとめておこうと思います。

Bearer Token を Managed Identity で取得できるようになると、Azure AD を使った認証をユーザーと関係ない部分で簡単に使えるようになるので、認証周りのパターンを作れると便利になると思っています。