2013年7月22日月曜日

GAE + Go で Sign in with Facebook (2)

前回は Facebook にアプリを登録して Client ID と Client Secret を発行してもらいました。
GAE + Go で Sign in with Facebook

続いて実際にコードを書いていきます。
ちなみに今回は OAuth 2.0 の認証コード方式を使います。

まずログインボタンを設置します。
ボタンの画像はサードパーティから無料で提供されているものを使用しました。
リンク先を /login_facebook とします。
<a href="/login_facebook"><img src="/client/login/img/sign_in_with_facebook.png"></a>



/login_facebook に対応するハンドラを作成します。
http.HandleFunc から呼び出します。
// Facebook のログインボタンが押された時の処理
func (this *Controller) LoginFacebook(w http.ResponseWriter, r *http.Request) {
 c := appengine.NewContext(r)
 oauth := NewOAuth2(c, FACEBOOK_CLIENT_ID, FACEBOOK_CLIENT_SECRET)
 oauth.RequestAuthorizationCode(w, r, "https://www.facebook.com/dialog/oauth", url.QueryEscape("http://escape-3ds.appspot.com/callback_facebook"))
}

Facebook から発行された Client IDClient Secret を使って通信します。
最初に、アプリがユーザデータへアクセスする許可をユーザから得る必要があります。
Facebook ではユーザの許可を得る画面を OAuth Dialog と読んでいます。
OAuth Dialog の URL は http://www.facebook.com/dialog/oauth です。
ここに Client ID, Client Secret,  許可をもらった時のリダイレクト URL を GET で渡します。

詳しくはこちらをご覧ください
OAuth Dialog - Facebook Developers


上のコードでは自作のライブラリを使用しています。
中身はこんな感じになっています。
// OAuth 2.0
type OAuth2 struct {
 context appengine.Context
 clientId string
 clientSecret string
}

// OAuth2.0 インスタンスを返す
func NewOAuth2(c appengine.Context, clientId string, clientSecret string) *OAuth2 {
 oauth := new(OAuth2)
 oauth.context = c
 oauth.clientId = clientId
 
 oauth.clientSecret = clientSecret
 return oauth
}

// 認証コードをリクエストする
// 認証ページヘのリダイレクトを行いユーザに認証してもらう
// 認証が完了したらリダイレクトURIへ認証コードが返ってくる
func (this *OAuth2) RequestAuthorizationCode(w http.ResponseWriter, r *http.Request, targetUri string, redirectUri string) {
 targetUri = fmt.Sprintf("%s?client_id=%s&redirect_uri=%s&response_type=code", targetUri, this.clientId, redirectUri)
 http.Redirect(w, r, targetUri, 302)
}

今回はリダイレクト先として http://escape-3ds.appspot.com/callback_facebook を設定しました。
自分のアプリに合わせてコールバック先の URL を設定してください。
コールバックされた後の処理を書きます。
こちらも http.HandleFunc() で呼び出されるようにしましょう。
// Facebook でユーザがアクセスを許可した時に呼び出されるコールバック関数
func (this *Controller) CallbackFacebook(w http.ResponseWriter, r*http.Request) {
 c := appengine.NewContext(r)
 userInfo := this.RequestFacebookToken(w, r)
 
 model := NewModel(c)
 view := NewView(c, w)
 
 key := ""
 if model.ExistOAuthUser("Facebook", userInfo["oauth_id"]) {
  // 既存ユーザ
  params := make(map[string]string, 2)
  params["OAuthId"] = userInfo["oauth_id"]
  params["Type"] = "Facebook"
  key = model.GetUserKey(params)
 } else {
  // 新規ユーザ
  params := make(map[string]string, 4)
  params["user_type"] = "Facebook"
  params["user_name"] = userInfo["name"]
  params["user_oauth_id"] = userInfo["oauth_id"]
  params["user_pass"] = ""
  user := model.NewUser(params)
  key = model.AddUser(user)
 }
 
 if this.GetSession(c, r) == "" {
  this.StartSession(w, r, key)
 }
 view.Gamelist(key)
}

ユーザからの許可が得られたら、アクセストークンを Facebook に要求します。
上のコードでは RequestFacebookToken() で要求しています。
このとき、ユーザIDとユーザ名が含まれたレスポンスが Facebook から返されます。
データベース上にユーザID が存在するかどうかを調べて、新規ユーザかどうかを判断しています。
アクセストークンの取得先 URL は https://graph.facebook.com/oauth/access_token です。

アクセストークンについてはこちら
Access Tokens - Facebook Developer

RequestFacebookToken() の中身はこんな感じ。
// Facebook へアクセストークンを要求する。
// この関数は Facebook から認証コードをリダイレクトで渡された時に呼ばれる。
// ユーザ情報を格納した map を返す。
func (this *Controller) RequestFacebookToken(w http.ResponseWriter, r *http.Request) map[string]string {
 c := appengine.NewContext(r)
 code := r.FormValue("code")
 oauth := NewOAuth2(c, FACEBOOK_CLIENT_ID, FACEBOOK_CLIENT_SECRET)
 token := oauth.RequestAccessToken(w, r, "https://graph.facebook.com/oauth/access_token", url.QueryEscape("http://escape-3ds.appspot.com/callback_facebook"), code)
 response := oauth.RequestAPI(w, "https://graph.facebook.com/me", token)
 
 // JSON を解析
 type UserInfo struct {
  Id string `json:"id"`
  Name string `json:"name"`
 }
 userInfo := new(UserInfo)
 err := json.Unmarshal(response, userInfo)
 Check(c, err)
 
 result := make(map[string]string, 2)
 result["oauth_id"] = userInfo.Id
 result["name"] = userInfo.Name
 
 return result
}

RequestAccessToken() でアクセストークンとユーザ情報を取得して、
レスポンスのユーザデータのパースしています。

RequestAccessToken() の中身はこんな感じ。
// アクセストークンをリクエストする
// 引き換えとして認証コードを渡すこと
func (this *OAuth2) RequestAccessToken(w http.ResponseWriter, r *http.Request, targetUri string, redirectUri string, code string) string {
 targetUri = fmt.Sprintf("%s?client_id=%s&redirect_uri=%s&client_secret=%s&code=%s", targetUri, this.clientId, redirectUri, this.clientSecret, code)
 
 params := make(map[string]string, 4)
 params["client_id"] = this.clientId
 params["redirect_uri"] = redirectUri
 params["client_secret"] = this.clientSecret
 params["code"] = code
 response := Request(this.context, "GET", targetUri, params, "")
 
 body := make([]byte, 1024)
 _, err := response.Body.Read(body)
 Check(this.context, err)
 
 // response: oauth_token=******&expires=******
 responseParams := strings.Split(string(body), "&")
 tokenParam := strings.Split(responseParams[0], "=")
 return tokenParam[1]
}

すべてのコードは GitHub においてあります。
https://github.com/yokano/escape3ds

OAuth 2.0 に関するコントローラは
server/controller/controller.go
server/controller/oauth.go

ライブラリは
server/lib/oauth2.go

にあります。

0 件のコメント:

コメントを投稿