[Azure] 在現有的MVC應用程式中加入Azure AD帳號的登入整合功能

現在已經有越來越多的應用程式開始使用Azure AD的帳號登入驗證機制了
若是在公司或是自己手邊的應用程式要加入Azure AD的帳號驗證,說實話是個頗麻煩的動作

本篇文章會一步一步說明怎麼整合Azure AD帳號的驗證到現有的MVC專案中,基本上照著步驟執行應該是都沒有問題的

要整合Azure AD到現有的應用程式中,有兩個大步驟要執行,第一個是必須先註冊一個應用程式到公司的AAD裡,第二則是在應用程式中加入一些程式碼以完成登入的動作

在公司的AAD中註冊應用程式,依照下面的步驟就可以完成

1.打開Azure Portal,並進入到公司的Azure AD項目中

2.在Azure AD的服務項目中,點選[應用程式註冊]=>[新增註冊]

3.在新增註冊的頁面中給予一個應用程式的名稱,支援的帳戶類型,如果要允許任何帳戶都可以使用的話,可以選第二項,如果要連一般使用者或是個人帳號都可以使用,就選擇第三項。而重新導向URI的部份,先選擇[Web],並輸入應用程式的實際網址。當然現在也可以先不給,之後再提供也可以

4.按下註冊後,先將下面框起來的[應用程式識別碼]、[目錄識別碼]記下來,等一下會用到

5.接著進入[驗證]的功能,然後按下[新增平台]=>[Web 應用程式]的項目在設定Web應用程式的頁面中,設定[重新導向 URI]的欄位,這個值會是在登入完成後導頁到網站的網址,另外,[識別碼權仗]也必須要打勾起來,不然無法取得登入者的資訊

6.最後,回到Azure AD的主畫面中,把[主要網域]的網址複製下來,等一下也會用到

到這邊,Azure AD上的設定就已經完成了,接下來要在現有的應用程式中加入Azure AD登入帳號的驗證功能

1.在現有專案中的App_Start資料夾,加入[Startup.Auth.cs]的類別檔在檔案中加入下面的內容

namespace MyWebApp
{
    using Microsoft.Owin.Security;
    using Microsoft.Owin.Security.Cookies;
    using Microsoft.Owin.Security.OpenIdConnect;
    using Owin;

    public partial class Startup
    {
        private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
        private static string aadInstance = EnsureTrailingSlash(ConfigurationManager.AppSettings["ida:AADInstance"]);
        private static string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
        private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
        private static string authority = aadInstance + "common";

        public void ConfigureAuth(IAppBuilder app)
        {
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.UseCookieAuthentication(new CookieAuthenticationOptions());

            app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions
                {
                    ClientId = clientId,
                    Authority = authority,
                    PostLogoutRedirectUri = postLogoutRedirectUri
                });
        }

        private static string EnsureTrailingSlash(string value)
        {
            if (value == null)
            {
                value = string.Empty;
            }

            if (!value.EndsWith("/", StringComparison.Ordinal))
            {
                return value + "/";
            }

            return value;
        }
    }
}

2.在專案的根目錄,加入[Startup.cs]接著在檔案中加入下面內容

namespace MyWebApp
{
    using Owin;
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            ConfigureAuth(app);
        }
    }
}

3.在Controller的目錄下,新增[AccountController.cs]的MVC控制器並在控制器中加入下面的程式碼內容

namespace MyWebApp.Controllers
{
    using Microsoft.Owin.Security;
    using Microsoft.Owin.Security.OpenIdConnect;
    using Microsoft.Owin.Host.SystemWeb;
    using Microsoft.Owin.Security.Cookies;

    public class AccountController : Controller
    {
        public void SignIn()
        {
            // Send an OpenID Connect sign-in request.
            if (!Request.IsAuthenticated)
            {
                HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" },
                    OpenIdConnectAuthenticationDefaults.AuthenticationType);
            }
        }

        public void SignOut()
        {
            string callbackUrl = Url.Action("SignOutCallback", "Account", routeValues: null, protocol: Request.Url.Scheme);

            HttpContext.GetOwinContext().Authentication.SignOut(
                new AuthenticationProperties { RedirectUri = callbackUrl },
                OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
        }

        public ActionResult SignOutCallback()
        {
            if (Request.IsAuthenticated)
            {
                // Redirect to home page if the user is authenticated.
                return RedirectToAction("Index", "Home");
            }

            return View();
        }
    }
}

4.在畫面的_Layout.cshtml中適當的位置,加入下面作為登入資訊的內容

@if (Request.IsAuthenticated)
{
    <text>
        <ul class="nav navbar-nav navbar-right">
            <li class="navbar-text">
                Hello, @User.Identity.Name!
            </li>
            <li>
                @Html.ActionLink("Sign out", "SignOut", "Account")
            </li>
        </ul>
    </text>
}
else
{
    <ul class="nav navbar-nav navbar-right">
        <li>@Html.ActionLink("Sign in", "SignIn", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
    </ul>
}

5.最後,在web.config的檔案中,加入下面的appSettings的內容,分別用前面在Azure AD的設定項目中的值,代入到設定內容裡

<add key="ida:ClientId" value="[在這裡放入應用程式識別碼]" />
<add key="ida:AADInstance" value="https://login.microsoftonline.com/" />
<add key="ida:Domain" value="[在這裡放入主要網域]" />
<add key="ida:TenantId" value="[在這裡放入目錄識別碼]" />
<add key="ida:PostLogoutRedirectUri" value="[登出時所使用的網址]" />

到這邊,所有程式碼要加入的內容也都完成了。其中有一些Nuget套件需要安裝的,就請自行進行套件的安裝,我就不再多作說明

程式碼編譯完成後,就可以實際將程式執行起來看一看結果在畫面的右上角,有出現剛剛加在_Layout.cshtml中的登入文字

點選登入文字後,跳至微軟的登入畫面登入完成之後,會出現是否要授權這個應用程式存取帳號的權限,若是不授予的話就無法使用了,所以點選[接受]就可以

接著頁面導回到應用程式的頁面,可以看到應用程式已經抓到了登入者的帳號資訊,也可以點選登出的連結文字進行登出

Microsoft Azure AD的登入整合,提供了一個SaaS服務的平台多一種的帳號統一驗證的機制,也可以在套用完成後快速的取得使用者資訊並加以應用,是個非常方便的帳號管理機制,也不用擔心帳號密碼存放在自己的資料庫是否會有資安問題了

參考資料
將 App Service 或 Azure Functions 應用程式設定為使用 Azure AD 登入
登入使用者的 Web 應用程式:應用程式註冊
登入使用者的 Web 應用程式:程式碼設定
HttpContextBase 找不到 GetOwinContext 定義?!