使用 OAuth 提供者與 MVC 4

作者:Tom FitzMacken

本教學課程說明如何建置 ASP.NET MVC 4 Web 應用程式,讓使用者能夠使用外部提供者的認證登入,例如 Facebook、Twitter、Microsoft 或 Google,然後將這些提供者的某些功能整合到您的 Web 應用程式中。 為了簡單起見,本教學課程著重于使用 Facebook 的認證。

若要在 ASP.NET MVC 5 Web 應用程式中使用外部認證,請參閱 使用 Facebook 和 Google OAuth2 和 OpenID 登入建立 ASP.NET MVC 5 應用程式

在您的網站中啟用這些認證可提供顯著的優點,因為數百萬名使用者已經擁有這些外部提供者的帳戶。 如果使用者不需要建立並記住一組新的認證,這些使用者可能會更傾向註冊您的網站。 此外,在使用者透過其中一個提供者登入之後,您可以從提供者納入社交作業。

您將建置什麼

本教學課程中有兩個主要目標:

  1. 讓使用者使用 OAuth 提供者的認證登入。
  2. 從提供者擷取帳戶資訊,並將該資訊與網站的帳戶註冊整合。

雖然本教學課程中的範例著重于使用 Facebook 作為驗證提供者,但您可以修改程式碼以使用任何提供者。 實作任何提供者的步驟非常類似您在本教學課程中看到的步驟。 只有在直接呼叫提供者的 API 集時,您才會注意到顯著的差異。

必要條件

Or

此外,本主題假設您對 ASP.NET MVC 和 Visual Studio 有基本知識。 如果您需要 ASP.NET MVC 4 簡介,請參閱 ASP.NET MVC 4 簡介

建立專案

在 Visual Studio 中,建立新的 ASP.NET MVC 4 Web 應用程式,並將它命名為 「OAuthMVC」。 您可以將目標設為 .NET Framework 4.5 或 4。

建立專案

在 [新增 ASP.NET MVC 4 專案] 視窗中,選取 [ 網際網路應用程式 ],並將 Razor 保留為檢視引擎。

選取 [網際網路應用程式]

啟用提供者

當您使用網際網路應用程式範本建立 MVC 4 Web 應用程式時,專案會以 App_Start 資料夾中名為 AuthConfig.cs 的檔案建立。

AuthConfig 檔案

AuthConfig 檔案包含程式碼,可註冊外部驗證提供者的用戶端。 根據預設,此程式碼會標記為批註,因此不會啟用任何外部提供者。

public static class AuthConfig
{
    public static void RegisterAuth()
    {
        // To let users of this site log in using their accounts from other sites such as Microsoft, Facebook, and Twitter,
        // you must update this site. For more information visit https://go.microsoft.com/fwlink/?LinkID=252166

        //OAuthWebSecurity.RegisterMicrosoftClient(
        //    clientId: "",
        //    clientSecret: "");

        //OAuthWebSecurity.RegisterTwitterClient(
        //    consumerKey: "",
        //    consumerSecret: "");

        //OAuthWebSecurity.RegisterFacebookClient(
        //    appId: "",
        //    appSecret: "");

        //OAuthWebSecurity.RegisterGoogleClient();
    }
}

您必須取消批註此程式碼,才能使用外部驗證用戶端。 您只會取消批註您想要包含在網站中的提供者。 在本教學課程中,您只會啟用 Facebook 認證。

public static class AuthConfig
{
    public static void RegisterAuth()
    {
        //OAuthWebSecurity.RegisterMicrosoftClient(
        //    clientId: "",
        //    clientSecret: "");

        //OAuthWebSecurity.RegisterTwitterClient(
        //    consumerKey: "",
        //    consumerSecret: "");

        OAuthWebSecurity.RegisterFacebookClient(
            appId: "",
            appSecret: "");

        //OAuthWebSecurity.RegisterGoogleClient();        
    }
}

請注意,上述範例中, 方法包含註冊參數的空字串。 如果您嘗試立即執行應用程式,應用程式會擲回引數例外狀況,因為參數不允許空字串。 若要提供有效值,您必須向外部提供者註冊您的網站,如下一節所示。

向外部提供者註冊

若要使用外部提供者的認證來驗證使用者,您必須向提供者註冊您的網站。 當您註冊您的網站時,您會收到參數 (,例如金鑰或識別碼,以及註冊用戶端時要包含的秘密) 。 您必須具有想要使用之提供者的帳戶。

本教學課程不會顯示您必須執行以向這些提供者註冊的所有步驟。 這些步驟通常並不困難。 若要成功註冊您的網站,請遵循這些網站上提供的指示。 若要開始註冊您的網站,請參閱開發人員網站:

向 Facebook 註冊您的網站時,您可以為網站網域和 "http://localhost/" URL 提供「localhost」,如下圖所示。 使用 localhost 適用于大部分的提供者,但目前不適用於 Microsoft 提供者。 對於 Microsoft 提供者,您必須包含有效的網站 URL。

註冊網站

在上圖中,已移除應用程式識別碼、應用程式秘密和連絡人電子郵件的值。 當您實際註冊您的網站時,這些值將會存在。 您會想要記下應用程式識別碼和應用程式密碼的值,因為您會將它們新增至您的應用程式。

建立測試使用者

如果您不考慮使用現有的 Facebook 帳戶來測試您的網站,您可以略過本節。

您可以在 Facebook 應用程式管理頁面中輕鬆地為您的應用程式建立測試使用者。 您可以使用這些測試帳戶來登入您的網站。 按一下左側流覽窗格中的 [ 角色 ] 連結,然後按一下 [ 建立 ] 連結,即可建立測試使用者。

建立測試使用者

Facebook 網站會自動建立您要求的測試帳戶數目。

從提供者新增應用程式識別碼和秘密

既然您已從 Facebook 收到識別碼和秘密,請返回 AuthConfig 檔案,並將它們新增為參數值。 如下所示的值不是實際值。

public static class AuthConfig
{
    public static void RegisterAuth()
    {
        //OAuthWebSecurity.RegisterMicrosoftClient(
        //    clientId: "",
        //    clientSecret: "");

        //OAuthWebSecurity.RegisterTwitterClient(
        //    consumerKey: "",
        //    consumerSecret: "");

        //OAuthWebSecurity.RegisterFacebookClient(
        //    appId: "",
        //    appSecret: "");

        //OAuthWebSecurity.RegisterGoogleClient();
    }
}

使用外部認證登入

您只需要在網站中啟用外部認證即可。 執行您的應用程式,然後按一下右上角的登入連結。 範本會自動辨識您已將 Facebook 註冊為提供者,並包含提供者的按鈕。 如果您註冊多個提供者,則會自動包含每一個提供者的按鈕。

外部登入

本教學課程未涵蓋如何自訂外部提供者的登入按鈕。 如需該資訊,請參閱 使用 OAuth/OpenID 時自訂登入 UI

按一下 [Facebook] 按鈕,以使用 Facebook 認證登入。 當您選取其中一個外部提供者時,系統會將您重新導向至該網站,並由該服務提示登入。

下圖顯示 Facebook 的登入畫面。 請注意,您使用 Facebook 帳戶登入名為 oauthmvcexample 的網站。

facebook 驗證

使用 Facebook 認證登入之後,頁面會通知使用者網站將可存取基本資訊。

要求許可權

選取 [ 移至應用程式] 之後,使用者必須註冊網站。 下圖顯示使用者以 Facebook 認證登入之後的註冊頁面。 使用者名稱通常會預先填入來自提供者的名稱。

此螢幕擷取畫面顯示 [註冊] 頁面,您可以在其中將 Facebook 帳戶與此應用程式產生關聯。

按一下 [註冊 ] 以完成註冊。 關閉瀏覽器。

您可以看到新的帳戶已新增至您的資料庫。 在 [伺服器總管] 中,開啟 DefaultConnection 資料庫,然後開啟 [資料表] 資料夾。

資料庫資料表

以滑鼠右鍵按一下 UserProfile 資料表,然後選取 [顯示資料表資料]。

顯示資料

您會看到您新增的新帳戶。 查看 webpage_OAuthMembership 資料表中的資料。 您會看到與您剛新增之帳戶的外部提供者相關的更多資料。

如果您只想要啟用外部驗證,就會完成。 不過,您可以進一步將提供者的資訊整合到新的使用者註冊程式中,如下列各節所示。

建立其他使用者資訊的模型

如前幾節所述,您不需要擷取任何額外的資訊,內建帳戶註冊才能運作。 不過,大部分的外部提供者都會傳回使用者的其他資訊。 下列各節說明如何保留該資訊,並將其儲存至資料庫。 具體來說,您將保留使用者完整名稱的值、使用者個人網頁的 URI,以及指出 Facebook 是否已驗證帳戶的值。

您將使用Code First 移轉來新增資料表來儲存其他使用者資訊。 您要將資料表新增至現有的資料庫,因此您必須先建立目前資料庫的快照集。 藉由建立目前資料庫的快照集,您稍後可以建立只包含新資料表的移轉。 若要建立目前資料庫的快照集:

  1. 開啟 套件管理員主控台
  2. 執行 命令 enable-migrations
  3. 執行命令 add-migration initial –IgnoreChanges
  4. 執行命令 update-database

現在,您將新增屬性。 在 Models 資料夾中,開啟 AccountModels.cs 檔案,然後尋找 RegisterExternalLoginModel 類別。 RegisterExternalLoginModel 類別會保存從驗證提供者傳回的值。 新增名為 FullName 和 Link 的屬性,如下所示。

public class RegisterExternalLoginModel
{
    [Required]
    [Display(Name = "User name")]
    public string UserName { get; set; }

    public string ExternalLoginData { get; set; }

    [Display(Name = "Full name")]
    public string FullName { get; set; }

    [Display(Name = "Personal page link")]
    public string Link { get; set; }
}

此外,在 AccountModels.cs 中,新增名為 ExtraUserInformation 的新類別。 這個類別代表將在資料庫中建立的新資料表。

[Table("ExtraUserInformation")]
public class ExternalUserInformation
{
    public int Id { get; set; }
    public int UserId { get; set; }
    public string FullName { get; set; }
    public string Link { get; set; }
    public bool? Verified { get; set; }
}

在 UsersCoNtext 類別中,新增下方反白顯示的程式碼,以建立新類別的 DbSet 屬性。

public class UsersContext : DbContext
{
    public UsersContext()
        : base("DefaultConnection")
    {
    }

    public DbSet<UserProfile> UserProfiles { get; set; }
    public DbSet<ExternalUserInformation> ExternalUsers { get; set; }
}

您現在已準備好建立新的資料表。 再次開啟套件管理員主控台,這次:

  1. 執行命令 add-migration AddExtraUserInformation
  2. 執行命令 update-database

新的資料表現在存在於資料庫中。

擷取其他資料

有兩種方式可以擷取其他使用者資料。 第一種方式是在驗證要求期間保留傳回的使用者資料。 第二種方式是特別呼叫提供者 API 並要求詳細資訊。 FullName 和 Link 的值會自動由 Facebook 傳回。 值,指出 Facebook 是否已透過呼叫 Facebook API 來擷取帳戶。 首先,您將填入 FullName 和 Link 的值,然後稍後,您將取得已驗證的值。

若要擷取其他使用者資料,請在Controllers資料夾中開啟AccountController.cs檔案。

此檔案包含記錄、註冊和管理帳戶的邏輯。 特別是,請注意稱為 ExternalLoginCallbackExternalLoginConfirmation的方法。 在這些方法中,您可以新增程式碼來自訂應用程式的外部登入作業。 ExternalLoginCallback方法的第一行包含:

AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(
    Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));

其他使用者資料會傳回從VerifyAuthentication方法傳回的AuthenticationResult物件的ExtraData屬性。 Facebook 用戶端在 ExtraData 屬性中包含下列值:

  • id
  • NAME
  • link
  • gender
  • accesstoken

其他提供者在 ExtraData 屬性中會有類似但稍微不同的資料。

如果使用者不熟悉您的網站,您將擷取一些額外的資料,並將該資料傳遞至確認檢視。 只有在使用者不熟悉您的網站時,方法中的最後一個程式碼區塊才會執行。 將以下程式碼:

return View("ExternalLoginConfirmation", new RegisterExternalLoginModel 
{ 
    UserName = result.UserName, 
    ExternalLoginData = loginData 
});

改用這行︰

return View("ExternalLoginConfirmation", new RegisterExternalLoginModel
{
    UserName = result.UserName,
    ExternalLoginData = loginData,
    FullName = result.ExtraData["name"],
    Link = result.ExtraData["link"]
});

這項變更只會包含 FullName 和 Link 屬性的值。

ExternalLoginConfirmation 方法中,將程式碼修改為反白顯示,以儲存其他使用者資訊。

if (user == null)
{
    // Insert name into the profile table
    UserProfile newUser = db.UserProfiles.Add(new UserProfile { UserName = model.UserName });
    db.SaveChanges();

    db.ExternalUsers.Add(new ExternalUserInformation 
    { 
        UserId = newUser.UserId, 
        FullName = model.FullName, 
        Link = model.Link 
    });
    db.SaveChanges();

    OAuthWebSecurity.CreateOrUpdateAccount(provider, providerUserId, model.UserName);
    OAuthWebSecurity.Login(provider, providerUserId, createPersistentCookie: false);

    return RedirectToLocal(returnUrl);
}
else
{
    ModelState.AddModelError("UserName", "User name already exists. Please enter a different user name.");
}

調整檢視

您從提供者擷取的其他使用者資料會顯示在註冊頁面中。

在 [檢視/帳戶]資料夾中,開啟ExternalLoginConfirmation.cshtml。 在使用者名稱的現有欄位下方,新增 FullName、Link 和 PictureLink 的欄位。

<li>
    @Html.LabelFor(m => m.FullName)
    @Html.TextBoxFor(m => m.FullName)
</li>
<li>
    @Html.LabelFor(m => m.Link)
    @Html.TextBoxFor(m => m.Link)
</li>

您現在已準備好執行應用程式,並使用儲存的其他資訊註冊新使用者。 您必須擁有尚未向網站註冊的帳戶。 您可以使用不同的測試帳戶,或刪除 UserProfile 中的資料列,並針對您想要重複使用的帳戶 webpages_OAuthMembership 資料表。 藉由刪除這些資料列,您將確保再次註冊帳戶。

執行應用程式並註冊新的使用者。 請注意,這次確認頁面包含更多值。

螢幕擷取畫面顯示您可以在將 Facebook 帳戶與應用程式建立關聯之後,輸入使用者名稱和其他資訊的位置。

完成註冊之後,請關閉瀏覽器。 查看資料庫,以注意到 ExtraUserInformation 資料表中的新值。

安裝適用于 Facebook API 的 NuGet 套件

Facebook 提供 API,您可以 呼叫 來執行作業。 您可以直接傳送 HTTP 要求,或使用安裝 NuGet 套件來協助傳送這些要求,來呼叫 Facebook API。 在本教學課程中會顯示使用 NuGet 套件,但安裝 NuGet 套件並不重要。 本教學課程說明如何使用 Facebook C# SDK 套件。 還有其他 NuGet 套件可協助呼叫 Facebook API。

[管理 NuGet 套件 ] 視窗中,選取 Facebook C# SDK 套件。

安裝套件

您將使用 Facebook C# SDK 來呼叫需要使用者存取權杖的作業。 下一節說明如何取得存取權杖。

擷取存取權杖

大部分的外部提供者會在驗證使用者的認證之後傳回存取權杖。 此存取權杖非常重要,因為它可讓您呼叫只能供已驗證的使用者使用的作業。 因此,當您想要提供更多功能時,擷取和儲存存取權杖是不可或缺的。

視外部提供者而定,存取權杖可能只限時有效。 為了確保您有有效的存取權杖,您會在每次使用者登入時擷取它,並將其儲存為會話值,而不是儲存到資料庫。

ExternalLoginCallback方法中,存取權杖也會傳回AuthenticationResult物件的ExtraData屬性。 將醒目提示的程式碼新增至 ExternalLoginCallback ,以將存取權杖儲存在 Session 物件中。 每次使用者以 Facebook 帳戶登入時,就會執行此程式碼。

[AllowAnonymous]
public ActionResult ExternalLoginCallback(string returnUrl)
{
    AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(
        Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
    if (!result.IsSuccessful)
    {
        return RedirectToAction("ExternalLoginFailure");
    }

    if (result.ExtraData.Keys.Contains("accesstoken"))
    {
        Session["facebooktoken"] = result.ExtraData["accesstoken"];
    }

    if (OAuthWebSecurity.Login(
        result.Provider, 
        result.ProviderUserId, 
        createPersistentCookie: false))
    {
        return RedirectToLocal(returnUrl);
    }

    if (User.Identity.IsAuthenticated)
    {
        // If the current user is logged in add the new account
        OAuthWebSecurity.CreateOrUpdateAccount(
            result.Provider,
            result.ProviderUserId, 
            User.Identity.Name);
        return RedirectToLocal(returnUrl);
    }
    else
    {
        // User is new, ask for their desired membership name
        string loginData = OAuthWebSecurity.SerializeProviderUserId(
            result.Provider, 
            result.ProviderUserId);
        ViewBag.ProviderDisplayName =
            OAuthWebSecurity.GetOAuthClientData(result.Provider).DisplayName;
        ViewBag.ReturnUrl = returnUrl;
        return View("ExternalLoginConfirmation", new RegisterExternalLoginModel
        {
            UserName = result.UserName,
            ExternalLoginData = loginData,
            FullName = result.ExtraData["name"],
            Link = result.ExtraData["link"]
        });    
    }
}

雖然此範例會從 Facebook 擷取存取權杖,但您可以透過名為 「accesstoken」 的相同金鑰,從任何外部提供者擷取存取權杖。

登出

預設 LogOff 方法會將使用者登出您的應用程式,但不會將使用者登出外部提供者。 若要將使用者登出 Facebook,並防止權杖在使用者登出之後保存,您可以將下列醒目提示的程式碼新增至 AccountController 中的 LogOff 方法。

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
    WebSecurity.Logout();
    if (Session["facebooktoken"] != null)
    {
        var fb = new Facebook.FacebookClient();
        string accessToken = Session["facebooktoken"] as string;
        var logoutUrl = fb.GetLogoutUrl(new { access_token = accessToken, next = "http://localhost:39852/" });

        Session.RemoveAll();
        return Redirect(logoutUrl.AbsoluteUri);
    }

    return RedirectToAction("Index", "Home");
}

您在 參數中 next 提供的值是使用者登出 Facebook 之後要使用的 URL。 在本機電腦上進行測試時,您會為本機月臺提供正確的埠號碼。 在生產環境中,您會提供預設頁面,例如 contoso.com。

擷取需要存取權杖的使用者資訊

既然您已儲存存取權杖並安裝 Facebook C# SDK 套件,您就可以將它們一起使用,向 Facebook 要求其他使用者資訊。 在 ExternalLoginConfirmation 方法中,傳遞存取權杖的值,以建立 FacebookClient 類別的實例。 要求目前已驗證使用者之 已驗證 屬性的值。 已驗證的屬性會指出 Facebook 是否已透過一些其他方式驗證帳戶,例如將訊息傳送至行動電話。 將此值儲存在資料庫中。

if (user == null)
{
    // Insert name into the profile table
    UserProfile newUser = db.UserProfiles.Add(new UserProfile { UserName = model.UserName });
    db.SaveChanges();

    bool facebookVerified;

    var client = new Facebook.FacebookClient(Session["facebooktoken"].ToString());
    dynamic response = client.Get("me", new { fields = "verified" });
    if (response.ContainsKey("verified"))
    {
        facebookVerified = response["verified"];
    }
    else
    {
        facebookVerified = false;
    }

    db.ExternalUsers.Add(new ExternalUserInformation 
    { 
        UserId = newUser.UserId, 
        FullName = model.FullName, 
        Link = model.Link, 
        Verified = facebookVerified 
    });
    db.SaveChanges();

    OAuthWebSecurity.CreateOrUpdateAccount(provider, providerUserId, model.UserName);
    OAuthWebSecurity.Login(provider, providerUserId, createPersistentCookie: false);

    return RedirectToLocal(returnUrl);
}

您必須再次刪除使用者資料庫中的記錄,或使用不同的 Facebook 帳戶。

執行應用程式,並註冊新的使用者。 查看 ExtraUserInformation 資料表以查看 Verified 屬性的值。

結論

在本教學課程中,您已建立與 Facebook 整合的網站,以進行使用者驗證和註冊資料。 您已瞭解為 MVC 4 Web 應用程式設定的預設行為,以及如何自訂該預設行為。