Monday, May 7, 2012

[FB] Facebook C# SDK Server-Side Authentication

fb_c_sharp之前寫了新版Facebook C# SDK的介紹,但是是使用Java Script去取得Access Token,在某些情況下很能會因為JS支援性而產生問題,於是還是需要有server side的認證方式取得access token,才比較穩當的在各種情況下都可以取得所需資訊,於是就發了這篇啦。


要避開使用Facebook JavaScript SDK,我找了一下,看來就是使用Server-Side Authentication這個方式才有辦法,這個server-side認證方式基本上是走OAuth 2.0的協定去跑,在2010時我已寫過文章"[Facebook] C# SDK Authentication"介紹過,串接方式蠻類似的,所以我就不會在這邊做過多介紹,不過上次沒貼code,這次會貼一下方便大家參考。



1. Redirect the user to the OAuth Dialog
第一步是將網頁轉到下面這個網址
https://www.facebook.com/dialog/oauth
並帶下列參數
client_id : 應用程式的App ID。
redirect_uri : 認證後要導回的Url。
scope : 向使用者取得的授權。
state : (非必要)可自行運用的不重複字串。

2. The user is prompted to authorize your application
若使用者尚未授權此APP,或是scope有修改,則會出現下面畫面讓使用者確認。
FBSDK06


3. The user is redirected back to your app
使用者授權的話,會向 redirect_uri 丟出下面參數回傳
state : 剛剛傳去的state(如果你有傳)
code : Facebook傳回產生的code

使用者拒絕的話,則會丟這些參數
error_reason=user_denied
error=access_denied
error_description=The+user+denied+your+request.
state : 剛剛傳去的state(如果你有傳)

像是這樣
FBSDK08


4. Exchange the code for a user access token
取得code後,再將code連同下列參數丟到這網址去(不是redirect)
https://graph.facebook.com/oauth/access_token
client_id : 應用程式的App ID。
redirect_uri : 認證後要導回的Url(需與Step 1 一樣)。
client_secret : 應用程式的App Secret。
code : Facebook傳回產生的code

成功的話,該頁面會生出下列資訊,裡面就是我們所要的access_token與這個token的有效時間expries
FBSDK07


5. Make requests to the Graph API
有Access Token後,就可以去撈facebook上使用者有授權的資訊,如同範例上的連結這樣使用。
https://graph.facebook.com/me?access_token=YOUR_USER_ACCESS_TOKEN



最後就是範例啦,請記得要有Facebook C# SDK才能用

FBTest.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="FBTest.aspx.cs" Inherits="FBTest" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
<script src='http://www.google.com/jsapi' type='text/javascript'></script>
<script language='javascript' type='text/javascript'>    google.load("jquery", "1.7.1");</script>
<script type='text/javascript'>
    $(document).ready(function () {
        window.fbAsyncInit = function () {
            FB.init({
                appId: 'YOUR_APP_ID', // App ID
                status: true, // check login status
                cookie: true, // enable cookies to allow the server to access the session
                xfbml: true  // parse XFBML
            });
        };
        // Load the 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 = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=YOUR_APP_ID";
        fjs.parentNode.insertBefore(js, fjs);
        } (document, 'script', 'facebook-jssdk'));
    });
</script>
</head>
<body>
<div id="fb-root"></div>
    <form id="form1" runat="server">
    <div>
        <asp:Button ID="Button1" runat="server" Text="Auth" onclick="Button1_Click" />
        <div class="fb-login-button" data-show-faces="true" data-width="400" data-max-rows="1"></div>
        <asp:Label ID="Label1" runat="server" Text=""></asp:Label>
    </div>
    </form>
</body>
</html>


FBTest.aspx.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Web;
using Facebook;

public partial class FBTest : System.Web.UI.Page
{
    #region Props
    public string AppId = "YOUR_APP_ID";
    public string AppSecret = "YOUR_APP_SECRET";
    public string RedirectUri = "http://localhost:45353/WebTest/FBTest.aspx";
    public string Scope = "email,publish_stream,user_photos";
    public string ReturnCode
    {
        get
        {
            return (string.IsNullOrEmpty(Request.QueryString["code"])) ? null : Request.QueryString["code"];
        }
    }
    public string ReturnError
    {
        get
        {
            return (string.IsNullOrEmpty(Request.QueryString["error"])) ? null : Request.QueryString["error"];
        }
    }
    public int Stages
    {
        get
        {
            if (Session["AuthStage"] != null)
                return int.Parse(Session["AuthStage"].ToString());
            else
                return (int)AuthStage.Initial;
        } 
        set { Session["AuthStage"] = value; }
    }
    #endregion
    protected void Page_Load(object sender, EventArgs e)
    {
        if(Stages==(int)AuthStage.Initial)
        {
            if(!string.IsNullOrEmpty(ReturnCode))
            {
                Stages = (int) AuthStage.GotCode;
                Session["AccessToken"] = GetAccessToken();
                CallFb();
            }
            if (!string.IsNullOrEmpty(ReturnError))
            {
                Stages = (int)AuthStage.GotError;
                Label1.Text = "Error when user is redirected back";
            }
        }
    }
    protected void Button1_Click(object sender, EventArgs e)
    {
        Stages = (int) AuthStage.Initial;
        string url = "https://www.facebook.com/dialog/oauth?client_id=" + AppId + "&redirect_uri=" + RedirectUri + "&scope="+Scope;
        Response.Redirect(url);
    }
    private void CallFb()
    {
        if (Session["AccessToken"] != null)
        {
            var accessToken = Session["AccessToken"].ToString();
            var client = new FacebookClient(accessToken);
            try
            {
                dynamic result = client.Get("me", new { fields = "name,id,updated_time,picture" });
                string name = result.name;
                string id = result.id;
                string updatedTime = result.updated_time;
                Label1.Text = name + " , " + id + " , " + updatedTime + " , " + result.picture;
            }
            catch (FacebookOAuthException ex)
            {
                Session.Remove("AccessToken");
                HttpRuntime.Cache.Remove("access_token");
                Label1.Text = (ex.Message.IndexOf("OAuthException - #190") > -1) ? "User has not authorized" : "unknow error";
            }
        }
        else
        {
            var client = new FacebookClient();
            dynamic me = client.Get("zuck");
            string firstName = me.first_name;
            string lastName = me.last_name;
            Label1.Text = firstName + " , " + lastName;
        }
    }
    private Dictionary<string, string> GetOauthTokens(string code)
    {
        Dictionary<string, string> tokens = new Dictionary<string, string>();
        string url = string.Format("https://graph.facebook.com/oauth/access_token?client_id={0}&redirect_uri={1}&client_secret={2}&code={3}",AppId, RedirectUri, AppSecret, code);
        HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
        using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
        {
            StreamReader reader = new StreamReader(response.GetResponseStream());
            string retVal = reader.ReadToEnd();

            Label1.Text += retVal;

            foreach (string token in retVal.Split('&'))
            {
                tokens.Add(token.Substring(0, token.IndexOf("=")),
                    token.Substring(token.IndexOf("=") + 1, token.Length - token.IndexOf("=") - 1));
            }
        }
        return tokens;
    }
    private string GetAccessToken()
    {
        if (HttpRuntime.Cache["access_token"] == null)
        {
            Dictionary<string, string> args = GetOauthTokens(Request.Params["code"]);
            HttpRuntime.Cache.Insert("access_token", args["access_token"], null, DateTime.Now.AddMinutes(Convert.ToDouble(args["expires"])), TimeSpan.Zero);
        }
        return HttpRuntime.Cache["access_token"].ToString();
    }
    public enum AuthStage
    {
        Initial,
        GotCode,
        GotError,
        GotAccesstoken
    } 
}

貼出前我有稍微修改一下,應該還是會動啦。

No comments:

Post a Comment