[FB/Google] 社群帳號登入,使用Javascript SDK的Sample Code

using Javascript SDK Login Facebook and Google

前言

最近開發FB、Google的社群帳號登入,踩了幾個雷Orz,寫一下筆記...

1.如果系統是網站的話,建議使用Javascript SDK,簡單好上手,如果明明是網站,卻使用Server Side方式實作FB、Google登入,網頁會導頁來導頁去

例如:UDN購物網站

這種方式如果使用者正在填寫表單、正在瀏覽某項商品,卻因為必須執行登入動作而被導頁來導頁去,個人覺得使用者體驗滿差(原本表單填到一半的資料全被導頁消失不見XD

而且Server Side方式實作社群帳號登入,我從ASP.net MVC4 看到MVC5,範本程式碼都變得不一樣,不太好上手

反觀Javascript SDK只要到FB、Google官網的Sample Code,複製貼上改一改,幾乎馬上能跑,開發效率比較快

2.不同的FB App(不同的FB App ID),即使同一位user登入,產生出來的userID會不一樣

相對的,Google登入目前無此問題

3.Google的憑證建立好後,如果後續有追加「 JavaScript 來源或重新導向 URI」仍舊可能發生如下錯誤 ※沒有把該Url加入白名單的錯誤

貌似 Google 的設定無法立即生效(?

此時,直接為新的Url 另外建立一個新的憑證(同樣專案即可)就可以了

4.不管Facebook或Google,在設定有效Url時,都不支持IP位址,就算它是公開,也不支持,所以只有 https://localhost (開發人員本機) 或 https://domainName (正式機網址),社群帳號登入才能運作。

5. Facebook應用程式的設定:https://developers.facebook.com/apps/  、  Google OAuth的設定:https://console.developers.google.com/

由於介面一直改版來改版去,我懶得截圖放部落格XD

請自行參考黑暗執行緒大大文章去設定:筆記:使用Facebook帳號登入ASP.NET MVC網站筆記:使用Google帳號登入ASP.NET MVC網站

不過我要補充的是,Facebook似乎有改版,上述文章中「Facebook API不像Google可以設多組URL」這句話

現在Facebook已經可以設定多組URL了↓

↑ ※ 2019-03-26追記,使用Javascript SDK的話,Facebook「有效的 OAuth 重新導向 URI」只要設定到https://DomainName:Port即可,Google也是如此設定。

6.經過我胡亂測試XD 發現

影響Facebook登入成功失敗的Url設定為「有效的OAuth重新導向URI」,畫面如上

Google則是要看「已授權的 JavaScript 來源」和「已授權的重新導向 URI」,其中如果你的Google登入採用Javascript SDK的話,只要設定好「已授權的 JavaScript 來源」正確即可。

但如果你採用Server端呼叫Google 登入API 的話,那連「已授權的重新導向 URI」同時也要設定,請參考以下畫面。

其餘的URL設定,就跟登入成功失敗沒什麼太大關係,頂多影響使用者看到的資訊,胡亂填寫也可以

如下:隱私政策網址,網站網址

以下是Facebook、Google使用Javascript SDK登入的Sample Code

2019.01.31追記:

Google+ API在2019-03就會收掉,Google Sign-in最新程式碼(2019年版本)請見另一篇文章: [Google Sign-In for Websites] 整合Google帳號登入 Javascript SDK 的使用方式,範例程式碼 with ASP.net MVC  

實作

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
</head>
<body>

 
    <div>
        Facebook登入:<input type="button"  value="Facebook登入" onclick="FBLogin();" />
    </div>
    <div>
        Google登入:<input type="button"  value="Google登入" onclick="GoogleLogin();" />
    </div>

    <script type="text/javascript">
        //應用程式編號,進入 https://developers.facebook.com/apps/ 即可看到
        let FB_appID = "";

        //FB Login 官方文件:https://developers.facebook.com/docs/facebook-login/web

        // Load the Facebook Javascript SDK asynchronously
        (function (d, s, id) {
            var js, fjs = d.getElementsByTagName(s)[0];
            if (d.getElementById(id)) return;
            js = d.createElement(s); js.id = id;
            js.src = "https://connect.facebook.net/en_US/sdk.js";
            fjs.parentNode.insertBefore(js, fjs);
        }(document, 'script', 'facebook-jssdk'));

        window.fbAsyncInit = function () {
            FB.init({
                appId: FB_appID,//FB appID
                cookie: true,  // enable cookies to allow the server to access the session
                
                xfbml: true,  // parse social plugins on this page
                version: 'v3.0' // use graph api version
            });

        };

        //使用自己客製化的按鈕來登入
        function FBLogin() {
            
            FB.login(function (response) {
                //debug用
                console.log(response);
                if (response.status === 'connected') {
                    //user已登入FB
                    //抓userID
                    let FB_ID = response["authResponse"]["userID"];
                    console.log("userID:" + FB_ID);

                   
                } else {
                    // user FB取消授權
                    alert("Facebook帳號無法登入");
                }
            }, { scope: 'public_profile,email' });

        }
    </script>



    <!--Google登入-->
    <script async defer src="https://apis.google.com/js/api.js" onload="this.onload=function(){};HandleGoogleApiLibrary()"
            onreadystatechange="if (this.readyState === 'complete') this.onload()"></script>
    <script type="text/javascript">
        //進入 https://console.developers.google.com/,找「憑證」頁籤(記得先選對專案),即可找到用戶端ID
        let Google_appId = "*****.apps.googleusercontent.com";


        //參考文章:http://usefulangle.com/post/55/google-login-javascript-api 

        // Called when Google Javascript API Javascript is loaded
        function HandleGoogleApiLibrary() {
            // Load "client" & "auth2" libraries
            gapi.load('client:auth2', {
                callback: function () {
                    // Initialize client & auth libraries
                    gapi.client.init({
                        clientId: Google_appId,
                        scope: 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/plus.me'
                    }).then(
                        function (success) {
                            // Google Libraries are initialized successfully
                            // You can now make API calls 
                            console.log("Google Libraries are initialized successfully");
                        },
                        function (error) {
                            // Error occurred
                            console.log(error);// to find the reason 
                        }
                    );
                },
                onerror: function () {
                    // Failed to load libraries
                    console.log("Failed to load libraries");
                }
            });
        }



        function GoogleLogin() {
            // API call for Google login  
            gapi.auth2.getAuthInstance().signIn().then(
                function (success) {
                    // Login API call is successful 
                    console.log(success);
                    let Google_ID = success["El"];
                   
                    
                },
                function (error) {
                    // Error occurred
                    // console.log(error) to find the reason
                    console.log(error);
                }
            );

        }



    </script>

    <!--有些網站會做帳號和user FB帳號的綁定/解除綁定,或你想讓使用者刪除你的FB App,讓使用者下次可以切換不同FB帳號登入你的網站-->
    <!--下面程式碼派得上用場-->
     <script type="text/javascript">
        
//刪除使用者已授權你的FB App,好讓使用者下次重新授權你的FB App
//參考:https://stackoverflow.com/questions/6634212/remove-the-application-from-a-user-using-graph-api/7741978#7741978
function Del_FB_App() { 
    FB.getLoginStatus(function (response) {//取得目前user是否登入FB網站
        //debug用
        console.log(response);
        if (response.status === 'connected') {
            // Logged into Facebook.
            //抓userID
            FB.api("/me/permissions", "DELETE", function (response) {
                console.log("刪除結果");
                console.log(response); //gives true on app delete success 
            });
        } else {
            // FB取消授權
            console.log("無法刪除FB App");
        }
    });


}

     </script>

     <!--類似上面Delete FB App的效果,呼叫此function後,下次使用者想再Google登入你的網站就必須重新選擇帳號-->
     <script type="text/javascript">
   
     //參考:https://developers.google.com/identity/sign-in/web/disconnect

     function Google_disconnect() {
    var auth2 = gapi.auth2.getAuthInstance();
    auth2.disconnect().then(function () {
        console.log('User disconnect.'); 
    });
     }


    </script>

</body>
</html>

↑ 上述的Del_FB_App()、Google_disconnect()兩個function執行後,user想再透過社群帳號登入你的網站,就會出現如下畫面

但這兩個方法並不是真的把使用者登出Facebook、Google網站,而是把使用者和自己的網站斷開連結的意思,如果真的要控制使用者登出FB、Google,另有其它寫法,請自行找API ~

2018.7.6 補充ASP.net WebForm的完整Sample Code,然後改善按鈕的外觀

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="WebApplication1Test.WebForm1" %>

<!DOCTYPE html>
<html>
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>ASP.net WebForm 社群帳號登入 Sample Code</title>
    <!-- Material Design的點擊漣渏效果,出處:
     https://github.com/xiaoyuze88/Ripples.js-from-bootstrap-material-design 
     -->
    <link href="ripples.css" rel="stylesheet" />
    <!--按鈕樣式,隨便調的,出處:
    http://materiallab.authenticgoods.co/login.html
    -->
    <style type="text/css">
    .login-options {
    text-align: center;
    margin: 25px auto 0;
    min-height: 15px;
    position: relative;
    color: #607188;
    font-family: "Open Sans",sans-serif;
    font-weight: 400;
    line-height: 1.8em;
    -webkit-font-smoothing: subpixel-antialiased;
    font-size: 100%;
}
    .login-options span {
    position: absolute;
    top: -9px;/*調整「Or」的上、下位置*/
    background: #fff;
    padding: 0 15px;
    margin: auto;
    left: 42%;
}
    hr {
    margin-top: 10px;
    margin-bottom: 10px;
    height: 1px;
    width: 100%;
    background: #E3ECF7;
}
    hr {
  
    border: 0;
    border-top: 1px solid #eee;
}
    .btn.btn-facebook, .btn.btn-facebook.active, .btn.btn-facebook.active:focus, .btn.btn-facebook.active:hover, .btn.btn-facebook:active, .btn.btn-facebook:active:focus, .btn.btn-facebook:active:hover, .btn.btn-facebook:focus, .btn.btn-facebook:hover, .navbar .navbar-nav>li>a.btn.btn-facebook, .navbar .navbar-nav>li>a.btn.btn-facebook.active, .navbar .navbar-nav>li>a.btn.btn-facebook.active:focus, .navbar .navbar-nav>li>a.btn.btn-facebook.active:hover, .navbar .navbar-nav>li>a.btn.btn-facebook:active, .navbar .navbar-nav>li>a.btn.btn-facebook:active:focus, .navbar .navbar-nav>li>a.btn.btn-facebook:active:hover, .navbar .navbar-nav>li>a.btn.btn-facebook:focus, .navbar .navbar-nav>li>a.btn.btn-facebook:hover, .open>.btn.btn-facebook.dropdown-toggle, .open>.btn.btn-facebook.dropdown-toggle:focus, .open>.btn.btn-facebook.dropdown-toggle:hover, .open>.navbar .navbar-nav>li>a.btn.btn-facebook.dropdown-toggle, .open>.navbar .navbar-nav>li>a.btn.btn-facebook.dropdown-toggle:focus, .open>.navbar .navbar-nav>li>a.btn.btn-facebook.dropdown-toggle:hover {
    background-color: #3b5998;
    color: #FFF;
}
 
 
 
    .btn {
    line-height: 1.42857143;
    text-align: center;
    white-space: nowrap;
    vertical-align: middle;
    touch-action: manipulation;
    cursor: pointer;
    user-select: none;
        background-image: none;
         border: none;
    border-radius: 3px;
    position: relative;
    padding: 12px 30px;
    margin: 10px 1px;
    text-transform: uppercase;
    letter-spacing: 0;
    
    transition: box-shadow .2s cubic-bezier(.4,0,1,1),background-color .2s cubic-bezier(.4,0,.2,1);
    outline: 0; 
    font-size: 14px;
    font-weight: 400; 
    will-change: box-shadow,transform;
    
     
}
    button {
    overflow: visible;
}
    .btn:not(.btn-fab) i {
    vertical-align: middle;
    font-size: 1.5em;
    position: relative;
    padding: 0 3px;
}
  
    .btn.btn-facebook:active, .btn.btn-facebook:focus, .btn.btn-facebook:hover, .navbar .navbar-nav>li>a.btn.btn-facebook:active, .navbar .navbar-nav>li>a.btn.btn-facebook:focus, .navbar .navbar-nav>li>a.btn.btn-facebook:hover {
    box-shadow: 0 14px 26px -12px rgba(59,89,152,.42), 0 4px 23px 0 rgba(0,0,0,.12), 0 8px 10px -5px rgba(59,89,152,.2);
}
    .btn.btn-google:active, .btn.btn-google:focus, .btn.btn-google:hover, .navbar .navbar-nav>li>a.btn.btn-google:active, .navbar .navbar-nav>li>a.btn.btn-google:focus, .navbar .navbar-nav>li>a.btn.btn-google:hover {
    box-shadow: 0 14px 26px -12px rgba(221,75,57,.42), 0 4px 23px 0 rgba(0,0,0,.12), 0 8px 10px -5px rgba(221,75,57,.2);
}
    .btn.btn-google, .btn.btn-google.active, .btn.btn-google.active:focus, .btn.btn-google.active:hover, .btn.btn-google:active, .btn.btn-google:active:focus, .btn.btn-google:active:hover, .btn.btn-google:focus, .btn.btn-google:hover, .navbar .navbar-nav>li>a.btn.btn-google, .navbar .navbar-nav>li>a.btn.btn-google.active, .navbar .navbar-nav>li>a.btn.btn-google.active:focus, .navbar .navbar-nav>li>a.btn.btn-google.active:hover, .navbar .navbar-nav>li>a.btn.btn-google:active, .navbar .navbar-nav>li>a.btn.btn-google:active:focus, .navbar .navbar-nav>li>a.btn.btn-google:active:hover, .navbar .navbar-nav>li>a.btn.btn-google:focus, .navbar .navbar-nav>li>a.btn.btn-google:hover, .open>.btn.btn-google.dropdown-toggle, .open>.btn.btn-google.dropdown-toggle:focus, .open>.btn.btn-google.dropdown-toggle:hover, .open>.navbar .navbar-nav>li>a.btn.btn-google.dropdown-toggle, .open>.navbar .navbar-nav>li>a.btn.btn-google.dropdown-toggle:focus, .open>.navbar .navbar-nav>li>a.btn.btn-google.dropdown-toggle:hover {
    background-color: #dd4b39;
    color: #FFF;
}
    .btn.focus, .btn:focus, .btn:hover {
    text-decoration: none;
}
 
 
 
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <asp:HiddenField runat="server" Value="" ID="hf_FB_ID" />
        <asp:HiddenField runat="server" Value="" ID="hf_Google_ID" />
        <!--這是可被jQuery選取到的隱藏按鈕-->
        <asp:Button runat="server" ID="btnSocialLogin" style="display:none;" OnClick="btnSocialLogin_Click" />

      
        
           <!--水平分隔線-->
          <div class="login-options">
            <span>OR</span>
            <hr>
          </div>
        
          <!--放兩顆按鈕-->
              <button type="button" class="btn btn-facebook" onclick="FBLogin();"><i class="fab fa-facebook-f fa-lg"></i> Facebook 登入 
              </button> 
              <button type="button" class="btn btn-google" onclick="GoogleLogin();"><i class="fab fa-google-plus-g fa-lg"></i> Google 登入 
              </button>
           
    
        <!--引用jQuery-->
         <script type="text/javascript" src="https://code.jquery.com/jquery-3.3.1.js"></script>
        <!--引用fontawesome,才能制作Facebook、Google的icon-->
        <script defer src="https://use.fontawesome.com/releases/v5.1.0/js/all.js" integrity="sha384-3LK/3kTpDE/Pkp8gTNp2gR/2gOiwQ6QaO7Td0zV76UFJVhqLl4Vl3KL1We6q6wR9" crossorigin="anonymous"></script>
       <!-- Material Design的點擊漣渏效果,出處:
         https://github.com/xiaoyuze88/Ripples.js-from-bootstrap-material-design 
        -->
        <script src="ripples.js"></script>


      
        
        
        <script type="text/javascript">
        //應用程式編號,進入 https://developers.facebook.com/apps/ 
        //即可看到
        let FB_appID = "xxxx36110";

         //FB Login 官方文件:https://developers.facebook.com/docs/facebook-login/web

        // Load the Facebook Javascript SDK asynchronously
        (function (d, s, id) {
            var js, fjs = d.getElementsByTagName(s)[0];
            if (d.getElementById(id)) return;
            js = d.createElement(s); js.id = id;
            js.src = "https://connect.facebook.net/en_US/sdk.js";
            fjs.parentNode.insertBefore(js, fjs);
        }(document, 'script', 'facebook-jssdk'));

        window.fbAsyncInit = function () {
            FB.init({
                appId: FB_appID,//FB appID
                cookie: true,  // enable cookies to allow the server to access the session
                
                xfbml: true,  // parse social plugins on this page
                version: 'v3.0' // use graph api version,儘量使用最新版,因為舊版淘汰很快
            });

        };
 

        //使用自己客製化的按鈕來登入
        function FBLogin() {
            
            FB.login(function (response) {
                //debug用
                console.log(response);
                if (response.status === 'connected') {
                    //user已登入FB
                    //抓userID
                    let FB_ID = response["authResponse"]["userID"];
                    console.log("userID:" + FB_ID);

                    //ASP.net WebForm的代碼
                    $("#<%=  hf_FB_ID.ClientID%>").val(FB_ID);
                    $("#<%=  hf_Google_ID.ClientID%>").val("");//清空Google_ID
                    $("#<%=  btnSocialLogin.ClientID %>").click(); //提交表單,觸發btnSocialLogin_Click後端事件
                   
                   
                } else {
                    // user FB取消授權
                    alert("Facebook帳號無法登入");
                }
            }, { scope: 'public_profile,email' });

        }
    </script>


    <!--Google登入-->
    <script async defer src="https://apis.google.com/js/api.js" onload="this.onload=function(){};HandleGoogleApiLibrary()"
            onreadystatechange="if (this.readyState === 'complete') this.onload()"></script>
    <script type="text/javascript"> 
        //進入 https://console.developers.google.com/
        //找「憑證」頁籤(記得先選對專案),即可找到用戶端ID
        let Google_appId = "xxxxxx.apps.googleusercontent.com";


        //參考文章:http://usefulangle.com/post/55/google-login-javascript-api 
         

        // Called when Google Javascript API Javascript is loaded
        function HandleGoogleApiLibrary() {
            // Load "client" & "auth2" libraries
            gapi.load('client:auth2', {
                callback: function () {
                    // Initialize client & auth libraries
                    gapi.client.init({
                        clientId: Google_appId,
                        scope: 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/plus.me'
                    }).then(
                        function (success) {
                            // Google Libraries are initialized successfully
                            // You can now make API calls 
                            console.log("Google Libraries are initialized successfully");
                        },
                        function (error) {
                            // Error occurred
                            console.log(error);// to find the reason 
                        }
                    );
                },
                onerror: function () {
                    // Failed to load libraries
                    console.log("Failed to load libraries");
                }
            });
        }
         
        function GoogleLogin() {
            // API call for Google login  
            gapi.auth2.getAuthInstance().signIn().then(
                function (success) {
                    // Login API call is successful 
                    console.log(success);
                    let Google_ID = success["El"];

                    //ASP.net WebForm的代碼
                    $("#<%=  hf_FB_ID.ClientID%>").val("");//清空FB userID
                    $("#<%=  hf_Google_ID.ClientID%>").val(Google_ID); 
                    $("#<%=  btnSocialLogin.ClientID %>").click(); //提交表單,觸發btnSocialLogin_Click後端事件
                },
                function (error) {
                    // Error occurred
                    // console.log(error) to find the reason
                    console.log(error);
                }
            );

        }
         
    </script>

        <script type="text/javascript">
            $(function () {
                //Material Design的點擊漣渏效果,出處:https://github.com/xiaoyuze88/Ripples.js-from-bootstrap-material-design
                $('.btn').ripples();
            });
        </script>

    </form>
</body>
</html>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebApplication1Test
{
    public partial class WebForm1 : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
             
        }

        /// <summary>
        /// 社群按鈕button click事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void btnSocialLogin_Click(object sender, EventArgs e)
        {
            Response.Write($@"FB_ID:{hf_FB_ID.Value},Google_ID:{hf_Google_ID.Value}");
            //以下自行處理
            if (!string.IsNullOrEmpty(hf_FB_ID.Value) && string.IsNullOrEmpty(hf_Google_ID.Value))
            {//FB 登入

            }
            else if (string.IsNullOrEmpty(hf_FB_ID.Value) && !string.IsNullOrEmpty(hf_Google_ID.Value))
            {//Google 登入

            }
            
        }
    }
}

執行結果:

補充

網站整合社群帳號登入目前個人所知有兩種系統流程:

1.用戶在登入頁面一按下 「Facebook登入」按鈕,系統檢查DB會員Table,沒有就Insert一筆網站會員資料(系統自動幫用戶註冊網站會員的概念),如果DB已有該用戶資料的話(依FB UserID判斷)就抓出該筆網站會員資料,然後做網站登入動作(看是把會員資料塞進Session或ASP.net的表單驗證登入機制)。

優點:用戶不必填太多註冊資料,在登入頁即可一鍵登入網站(第一次FB登入系統就自動幫忙註冊為網站會員),系統流程簡單。

缺點:可能發生同一位用戶在DB裡有FB的網站會員+用戶自己申請的會員(同一個人在DB裡有兩筆會員資料,這點要看客戶能不能接受)

為了避免上述的缺點,產生了下述整合登入流程XD

2.用戶必須先成為網站會員,先申請註冊會員,把自己的會員詳細資料填完,然後系統準備一個頁面,讓用戶手動綁定社群帳號(系統記得檢查FB UserID是否重複被綁定)

如果用戶有手動綁定社群帳號(意即FB UserID關聯到該會員用戶),日後該用戶即可在登入頁按下「Facebook登入」按鈕一鍵登入。

優點:同一個人在DB 會員Table只有一筆資料。

缺點:用戶必須事先填寫會員資料註冊,才能使用社群帳號登入功能,系統多了一些流程給用戶操作。