เว็บแอพบน Azure App Service ให้ authen ด้วย Microsoft Authentication provider

การเข้าใช้งาน Web App ที่อยู่บน Azure App Service ให้บังคับ authen ด้วย Microsoft Authentication provider ก่อน ถึงจะใช้งานได้

  1. สร้าง Azure App Service
  2. ลงทะเบียน Web App ที่ Application Registration Portal
  3. คอนฟิก App Service ให้บังคับ Authentication
  4. อ่านค่า userid จาก .auth/me

1.สร้าง Azure App Service

ไปที่ Portal เลือกเมนู Create a resource > Web > Web App

ตั้งชื่อ jackwebapp แล้วจะได้ url เป็น jackwebapp.azurewebsites.net

Note: ตรง Operating System ถ้าเลือก Windows เมื่อสร้าง App Service เสร็จ แล้วเข้ามาแก้ไข Configuration > Stack Settings จะมีให้เลือกแค่ .NET, PHP, Python และ Java แต่ถ้า
ตรง Operating System เลือก Linux ที่ Stack Settings จะมีให้เลือกเพียบเลย

เมื่อสร้างเสร็จ กด Browse

หรือเรียกไปที่ https://jackwebapp.azurewebsites.net/

ขึ้นแบบนี้แสดงว่าเว็บพร้อมใช้งานละ

2.ลงทะเบียน Web App ที่ Application Registration Portal

ลงทะเบียน Application ที่ https://apps.dev.microsoft.com/

กด Add an app

แล้วตั้งชื่อเช่น JackWebAppRegis (ชื่อนี้จะแสดงให้ user เห็นตอน login ฉะนั้นตั้งให้ friendly หน่อย)

หน้าถัดมาจะเห็น Application Id แต่ยังไม่มี password

กดปุ่ม Generate new password แล้วจด password ไว้

เสร็จแล้ว Add Platform แล้วเลือก Web

ตรง Redirect URLs ให้ใส่ รูปแบบนี้

https://<your_string>.azurewebsites.net/.auth/login/microsoftaccount/callback

เช่น

https://jackwebapp.azurewebsites.net/.auth/login/microsoftaccount/callback

กด Save

3. คอนฟิก App Service ให้บังคับ Authentication

กลับมาที่ Portal เข้า App Service ที่เราสร้าง (jackwebapp)

เลือก Authentication / Authorization

กด On แล้วเลือก Microsoft

ใส่ค่า Client id และ Client Secret ที่ได้มาจากตอนลงทะเบียน application ในขั้นตอนที่ 2 แล้วกด OK จะกลับมาที่หน้า Authentication / Authorization

ที่ Action to take when request is not authenticated ให้เลือกเป็น Log in with Microsoft Account แล้วกด Save

ตรงนี้เป็นการบังคับให้ต้อง authen ด้วย Microsoft account ก่อนถึงจะเข้าใข้เว็บได้

แต่ถ้าจะไม่บังคับก็ให้เลือกเป็น Allow Anonymous requests (no action)

ทีนี้พอเข้าเว็บเราอีกที https://jackwebapp.azurewebsites.net/ ก็จะ redirect ไปให้ login ด้วย Microsoft account ก่อน

เมื่อใส่ user & pass เสร็จ ก็จะมีการขอสิทธิเข้าถึงเบื้องต้น

กด Yes ก็จะเข้าเว็บ https://jackwebapp.azurewebsites.net/ ได้ละ

ถ้าจะดูว่าเราได้ claim อะไรบ้างก็ดูที่ https://jackwebapp.azurewebsites.net/.auth/me เช่น

  • user_id
  • user_claims
  • provider_name
  • access_token
  • expires_on

Note: การคอนฟิก App Service ให้ใช้ Authentication เป็นการทำ Single Sign-On ไปในตัว ไม่ว่าจะบังคับ (Log in with Microsoft Account ) หรือ ไม่บังคับ (Allow Anonymous requests (no action)) ก็ทำให้สามารถใช้ browser เข้าไปดูไฟล์ หรือ deploy ไฟล์ .zip ได้ โดยเข้าไปที่

https://jackwebapp.scm.azurewebsites.net/ZipDeployUI

แต่ถ้าไม่ได้ login หรือ user ที่ login ไม่มีสิทธิ จะฟ้อง error ว่า

You are not authorized or do not have any subscriptions associated with your account.

4.อ่านค่า userid จาก .auth/me

วิธีที่ใช้คืออ่านค่าจาก .auth/me ไมไ่ด้ใช้ Azure Mobile Apps: .NET Client SDK เพราะดูแล้วน่าจะง่ายกว่าเพราะเราใช้ .NET Core แต่ Package ที่ SDK ใช้ยังเป็น .NET Framework

แต่ถ้าเป็น mobile apps ไม่แน่ใจ ต้องลองดูที่ Authentication and authorization in Azure App Service for mobile apps อีกที

สร้างโปรเจ็กส์แบบ Web Application ด้วย .NET Core 2.1

Publish เว็บแอพไป Azure App Service ด้วย Visual Studio

ลองรันดู ถ้ารันได้ ก็ OK แต่ไม่รู้ว่า user ที่เข้ามาเป็นใคร ดังนั้นต่อมาเราจะหา userid

สร้างคลาส Global ไว้เก็บค่า userid

namespace JackWebApp
{
    public class Global
    {
        public static string userid;
        public static string username;
    }
}

ที่ไฟล์ Startup.cs แก้ไข Configure() ให้รับค่ามาจาก endpoint .auth/me

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Security.Principal;

namespace JackWebApp
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });


            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.Use(async (context, next) =>
            {
                //Global.userid += "0";
                // Create a user on current thread from provided header
                if (context.Request.Headers.ContainsKey("X-MS-CLIENT-PRINCIPAL-ID"))
                {
                    //Global.userid += "1";
                    // 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 = string.Empty;
                    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
                    string provider_name = obj[0]["provider_name"].Value<string>();
                    Global.username = user_id;

                    // Create claims id
                    List<Claim> claims = new List<Claim>();
                    foreach (var claim in obj[0]["user_claims"])
                    {
                        if (provider_name == "microsoftaccount")
                        {
                            if (claim["typ"].ToString() == "urn:microsoftaccount:id")
                            {
                                Global.userid = claim["val"].ToString();
                            }
                        }

                        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();
            });

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseMvc();
        }
    }
}

แสดงค่า userid ที่ About.cshtml.cs

using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Linq;
using System.Security.Claims;

namespace TipWebApp.Pages
{
    public class AboutModel : PageModel
    {
        public string Message { get; set; }

        public void OnGet()
        {
            //Message = "Your application description page.";

            Message = Global.userid + " " + Global.username;


            ////Message = HttpContext.
            //ClaimsPrincipal claimsPrincipal = HttpContext.User;

            //// Get the claims values
            ////var name = identity.Claims.Where(c => c.Type == ClaimTypes.Name)
            ////                   .Select(c => c.Value).SingleOrDefault();
            ////var sid = identity.Claims.Where(c => c.Type == ClaimTypes.Sid)
            ////                   .Select(c => c.Value).SingleOrDefault();
            //var userid = claimsPrincipal.Claims.Where(c => c.Type == "urn:microsoftaccount:id")
            //                   .Select(c => c.Value).SingleOrDefault();
            //var username = claimsPrincipal.Claims.Where(c => c.Type == "urn:microsoftaccount:name")
            //                   .Select(c => c.Value).SingleOrDefault();
            //Message = "[" + userid + " " + username + "]";
        }
    }
}

ทีนี้พอ publish ขึ้นไปใหม่ ก็จะเห็น userid และชื่อ ที่หน้า about ละ

https://jackwebapp.azurewebsites.net/About