Azureの小ネタ (改)

~Azureネタを中心に、色々とその他の技術的なことなどを~

Azure Web Apps の Easy Auth 情報をASP.NET Coreで受け取る

Azure App Service の Web AppsにはAzure ADで簡単に認証を設定できたりします(Easy Authというらしい)。

Azure App Service での認証と承認 | Microsoft Docs

これを使うとアプリ側に認証コード書かなくて済むのと、ASP.NET (Coreじゃない) の場合は、

すべての言語フレームワークで、App Service はユーザーの要求を要求ヘッダーに挿入することによってコードで要求を使用できるようにします。 ASP.NET 4.6 アプリの場合、App Service は認証されたユーザーの要求で ClaimsPrincipal.Current を設定するので、標準の .NET コード パターン ([Authorize] 属性など) に従うことができます。

フレームワーク側でPrincipalに適宜情報を設定してくれたりしますが、ASP.NET Core 2.xではサポートされていません。どうすればいいのかググってたらいくつか方法があったので、備忘録をブログっておきます。

stackoverflow の以下の記事。

stackoverflow.com

抜粋して拡張メソッド化すると以下のような感じです。app.UseEasyAuth() とかすればOKです。

パイプライン途中でヘッダ情報からGenericPrincipalを生成して、Context.Userに設定する方法です。

   public static class ApplicationBuilderExtension
    {
        public static void UseEasyAuth(this IApplicationBuilder app)
        {
            app.Use(async (context, next) =>
            {
                // Create a user on current thread from provided header
                if (context.Request.Headers.ContainsKey("X-MS-CLIENT-PRINCIPAL-ID"))
                {
                    // Read headers from Azure
                    var azureAppServicePrincipalIdHeader = context.Request.Headers["X-MS-CLIENT-PRINCIPAL-ID"][0];
                    var azureAppServicePrincipalNameHeader = context.Request.Headers["X-MS-CLIENT-PRINCIPAL-NAME"][0];

                    // Create claims id
                    var claims = new Claim[]
                    {
                        new System.Security.Claims.Claim("http://schemas.microsoft.com/identity/claims/objectidentifier", azureAppServicePrincipalIdHeader),
                        new System.Security.Claims.Claim("name", azureAppServicePrincipalNameHeader)
                    };

                    // Set user in current context as claims principal
                    var identity = new GenericIdentity(azureAppServicePrincipalNameHeader);
                    identity.AddClaims(claims);

                    // Set current thread user to identity
                    context.User = new GenericPrincipal(identity, null);
                }
                await next.Invoke();
            });
        }
    }

もう1つは、こちらの記事です。

stackoverflow.com

こちらも抜粋すると以下の通り。上との違いは、/.auth/me にリクエストなげて自身の情報をゲットしてクレーム作っているとこでしょうか。

アプリケーションでは、/.auth/me を呼び出して認証されたユーザーの追加の詳細を取得することもできます。

 public static void UseEasyAuth2(this IApplicationBuilder app)
        {
            app.Use(async (context, next) =>
            {
                // Create a user on current thread from provided header
                if (context.Request.Headers.ContainsKey("X-MS-CLIENT-PRINCIPAL-ID"))
                {
                    // Read headers from Azure
                    var azureAppServicePrincipalIdHeader = context.Request.Headers["X-MS-CLIENT-PRINCIPAL-ID"][0];
                    var azureAppServicePrincipalNameHeader = context.Request.Headers["X-MS-CLIENT-PRINCIPAL-NAME"][0];

                    #region extract claims via call /.auth/me

                    //invoke /.auth/me
                    var cookieContainer = new CookieContainer();
                    HttpClientHandler handler = new HttpClientHandler()
                    {
                        CookieContainer = cookieContainer
                    };
                    string uriString = $"{context.Request.Scheme}://{context.Request.Host}";
                    foreach (var c in context.Request.Cookies)
                    {
                        cookieContainer.Add(new Uri(uriString), new Cookie(c.Key, c.Value));
                    }

                    string jsonResult;
                    using (HttpClient client = new HttpClient(handler))
                    {
                        var res = await client.GetAsync($"{uriString}/.auth/me");
                        jsonResult = await res.Content.ReadAsStringAsync();
                    }

                    //parse json
                    var obj = JArray.Parse(jsonResult);

                    string user_id = obj[0]["user_id"].Value<string>(); //user_id

                    // Create claims id
                    List<Claim> claims = new List<Claim>();
                    foreach (var claim in obj[0]["user_claims"])
                    {
                        claims.Add(new Claim(claim["typ"].ToString(), claim["val"].ToString()));
                    }

                    // Set user in current context as claims principal
                    var identity = new GenericIdentity(azureAppServicePrincipalIdHeader);
                    identity.AddClaims(claims);

                    #endregion

                    // Set current thread user to identity
                    context.User = new GenericPrincipal(identity, null);
                }

                await next.Invoke();
            });

リクエストごとに呼ばれるよりはキャッシュしたりしたほうが実用的でしょう。どちらの方法を使うかはケースバイケースで。