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只有一筆資料。
缺點:用戶必須事先填寫會員資料註冊,才能使用社群帳號登入功能,系統多了一些流程給用戶操作。