[ASP.net MVC 4] 使用微軟內建的facebook OAuth登入範本的補充資訊,以瘋狂賣客網站為例

[ASP.net MVC 4] 使用微軟內建的facebook OAuth登入範本的補充資訊,以瘋狂賣客網站為例

前言

facebook整合網站的登入功能,玩了一下

只是把自家網站登入權交給facebook,使用者有在facebook登入過的話,在自家網站點一下登入鈕就會自行登入

使用者登出facebook的話,在自家網站點了登入鈕後還要再輸入一次facebook的帳號和密碼,才會進行登入

 

整個流程大概是這樣(以瘋狂賣客網站為例)

Step 1使用者點擊facebook登入鈕(無須輸入左方的帳密)

image

Step 2 頁面轉到facebook網站,facebook網站發現使用者尚未登入則要求先登入(facebook網站判斷Cookie有無逾期),否則跳到第3步驟

image

Step 3 facebook使用者第一次接觸瘋狂賣客的App,使用者點擊「確定」的話 ,App授權成功,使用者點擊「取消」的話,App授權失敗

image

Step 4 承上,使用者點擊「確定」後,頁面轉回瘋狂賣客首頁

image

若點擊「取消」的話,畫面也是轉回首頁,使用者當做登入失敗

image

Step 5.承Step 3當使用者點擊「確定」後,使用者的facebook應用程式中心,會出現瘋狂賣客的App

,Step 3 就是依據應用程式中心有無該App來判斷要不要出現詢問授權的畫面

image

Step 6.當瘋狂賣客 App取得使用者授權後,瘋狂賣客網站還會抓取FB使用者資料至他們的會員資料表

※以下用一般會員登入原本有「修改密碼」欄位,因為我是用FB登入來創建此筆會員資料,所以才沒出現,我已經用「忘記密碼」功能,來確認只要知道密碼,FB登入者也有機會透過一般會員登入方式進入網站,換句話說,證明FB登入和一般會員登入用的會員資料表是共用同一張Table,科科

image

到目前為止會員資料表大概可推敲有以下欄位,FB登入會員和一般會員共用同一張表

(
 MemberID bigint identity primary key,--當PK用
 Account nvarchar(max) not null default(''),
 Pwd nvarchar(max) not null default(''),
 Name nvarchar(max) not null default(''),
 email nvarchar(max) not null default(''),
 FacebookUserID nvarchar(max) not null default('') --有值的話,表示此筆為FB登入創建的資料

)

Step 7 使用者下次到瘋狂賣客做facebook帳號登入時,這時候使用者已經登入過facebook網站(電腦有facebook Cookie),也授權瘋狂賣客的App存取

再點一次登入鈕,就會直接登入

image

只是以瘋狂賣客網站為例,使用者登入後,他們還會另寫一份Crazy Mike的Cookie到使用者電腦,用來下次自動登入瘋狂賣客網站的判斷

這部份跟facebook帳號登入整合沒什麼關係就不詳談了

image

image

 

 

 

實作

知道流程後,可以開始動工了

一開始我本來使用網路上別人寫好的FacebookScopedClient類別:OAuthWebSecurity With Facebook not using email permission as expected

發現,本機localhost測試功能正常,一旦部署至正式機(有正式DomainName)就會掛掉(發生Null Exception)

後來改拿微軟Visual Studio 內建的facebook登入範本來改,其實就可以了(也支援localhost本地端的測試)

詳見:Using OAuth Providers with MVC 4

建立範本前,注意自己的網站是跑什麼.net版本就建立該版本的網際網路應用程式範本

image

image

建好後,到範本網站的Bin資料夾底下找以下的.dll

image

自己的網站就加入以上那些.dll的參考

然後範本網站的App_Start資料夾下有一個AuthConfig.cs檔,也把它複製到自己的網站中的App_Start資料夾下

 

接著就可以為自己的網站開始動手寫程式加入facebook登入功能

Global.asax.cs

using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
 

namespace yourNamespace
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
    // visit http://go.microsoft.com/?LinkId=9394801
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            /*Global.asax.cs加這個*/
            AuthConfig.RegisterAuth();
          
        }
    }
}

AuthConfig.cs

using System.Collections.Generic;
using System.Linq;
using System.Text;
//引用這段
using Microsoft.Web.WebPages.OAuth;
 
using System.Configuration;
 
namespace yourNamespace
{
    public static class AuthConfig
    {
        public static void RegisterAuth()
        {
 
          //appId和appSecret的產生請見http://www.asp.net/mvc/tutorials/security/using-oauth-providers-with-mvc
            OAuthWebSecurity.RegisterFacebookClient(
                appId: "",
                appSecret: "");
           
          
        
        }
    }
}

接著View準備一個submit按鈕,用來Click登入,Controller完整代碼在最底下

 

預設FB會回傳7個欄位資料,如果想要抓額外資料的話

要注意符合以下幾點:

1.facebook app在Graph API Explorer中有設定相對應的權限(例如想要抓使用者的感情狀態就勾選user_relationships)

下圖是facebook app預設的權限

image

要怎麼到這個畫面,之前文章有寫過,墮性發作,偷懶不寫

請自行到這邊[C#/Facebook API] 利用Facebook API 發文,適合前後端平台(使用者無需輸入帳密方式)從「接著要設定允許此應用程式的權限」這行開始看

2.該欄位(感情狀態欄)使用者有填寫,(隱私設定 公開or本人 不管)

3.使用者有同意該facebook app的存取

4.自家網站要求使用者資料時,有帶access token和該欄位名稱

承上面的範例程式碼,所以要求額外欄位資料時

那個fields有哪些值,可以到Graph API Explorer查看即可。

image

要抓多個欄位就用逗號分隔:fields=relationship_status,address,about

 

※2013.8.2 追記

今天專案碰上一個需求

使用者原先要進入A頁,但A頁須使用者先進行登入才可以進去,所以程式把使用者導到B頁(做FB登入),然後要讓使用者做完FB登入後

將使用者導回A頁

解決辦法,呼叫callback action時給予returnUrl值,但在IE可以,Google Chrome的話,該值會是NULL,所以…↓

 

在A頁導到B頁時,傳一個QueryString,假設叫myUrl,它是http開頭A頁的絕對路徑

所以在B頁瀏覽器Url會看到如下:

http://www.domain.com/Login?myUrl=http://www.domain.com/A

然後是FB按鈕post的Action

image

然後在FB登入的Action方法ExternalLoginCallback

裡面,判斷使用者登入成功後

             

            string myUrl = "";
           //ie的話取returnUrl
            if (!string.IsNullOrEmpty(returnUrl))
            {
                Uri uri = new Uri(returnUrl);
                myUrl = HttpUtility.ParseQueryString(uri.Query).Get("myUrl");
            }
            else
            {//Google chrome
                Uri uri = new Uri(Request.UrlReferrer.OriginalString);
                myUrl = HttpUtility.ParseQueryString(uri.Query).Get("myUrl");
            }

               if (string.IsNullOrEmpty(myUrl))
                 {
                     //導至首頁
                     return RedirectToAction("Index", "Home");
                 }
                 else
                 {//先前Request的A頁
                     Response.Redirect(myUrl);
                 }

 

完整Controller代碼:

        internal class ExternalLoginResult : ActionResult
        {
            public ExternalLoginResult(string provider, string returnUrl)
            {
                Provider = provider;
                ReturnUrl = returnUrl;
            }

            public string Provider { get; private set; }
            public string ReturnUrl { get; private set; }

            public override void ExecuteResult(ControllerContext context)
            {
                OAuthWebSecurity.RequestAuthentication(Provider, ReturnUrl);
            }
        }
        /// <summary>
        /// FB按鈕Click登入
        /// </summary>
        /// <param name="form"></param>
        /// <returns></returns>
        [HttpPost]
        public ActionResult FBLogin(FormCollection form)
        {
            //myUrl是先前Request的Url,為2013.8.2 專案需求加的
            ViewData["myUrl"] = form["myUrl"];


            return new ExternalLoginResult("Facebook", Url.Action("ExternalLoginCallback", new { ReturnUrl = Request.UrlReferrer.OriginalString }));
        }
        
        public ActionResult ExternalLoginCallback(string returnUrl)
        {
            string myUrl = "";
           //ie的話取returnUrl
            if (!string.IsNullOrEmpty(returnUrl))
            {
                Uri uri = new Uri(returnUrl);
                myUrl = HttpUtility.ParseQueryString(uri.Query).Get("myUrl");
            }
            else
            {//Google chrome
                Uri uri = new Uri(Request.UrlReferrer.OriginalString);
                myUrl = HttpUtility.ParseQueryString(uri.Query).Get("myUrl");
            }


            AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
            if (!result.IsSuccessful)
            {//FB登入失敗
           
                
                //導回會員登入頁
                return RedirectToAction("Login","Member");
            }
            else
            {//FB登入成功
                //FB回傳的資料
                // id,username,name,link,gender,birthday,accesstoken,
                
                string fbID = result.ProviderUserId;
                string fbName = result.ExtraData["name"];
                string fbEmail = result.ExtraData["username"];
                
                //檢查DB有無此會員資料,依FacebookID(略
               
              IEnumerable<Member> members =  _dbContext.Members.Where(m=>m.FacebookID==fbID);
               
                if (!members.Any())
                {//DB沒有,建立(略
                    Member m = new Member();
                    //塞資料(略

   
                    _dbContext.Members.Add(m);
                    _dbContext.SaveChanges();
                }
                else
                {//DB已有,更新資料...
                      Member m = members.FirstOrDefault();
                      m.Name = fbName;
                      m.Email = fbEmail;
                
                    _dbContext.SaveChanges();
                }
                //取得DB裡的FB會員資料(依FacebookID)
                Member member = _dbContext.Members.Where(x => x.FacebookID == fbID).FirstOrDefault();
                 Session["Member"] = member;//幫User做登入

 

                 if (string.IsNullOrEmpty(myUrl))
                 {
                     //導至首頁
                     return RedirectToAction("Index", "Home");
                 }
                 else
                {//先前Request的Url
                    Response.Redirect(myUrl);
                 }
                
            }

            //預設導至首頁
            return RedirectToAction("Index", "Home");
              
        }

 

 

 

 

結語

還是自己親手寫過比較了解整個運作