Facebookログイン機能をWEBサイトにSDK等使わないで実装の流れ

Facebookログイン機能をWEBサイトに実装する際、SDK等を使わない方法で考えてみる。

現在、日本語で公式ドキュメント「[ログインフローを手作業で構築する](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow)」が存在するので、まずはそちらを見た方が良いかも。操作方法などは数ヶ月単位で時々変わっている。以前は設定画面も英語だけだったが、日本語化されている。

※ 実際、もうこの内容は古くなっていた(2017/10/11)。数ヶ月おきに画面デザインや項目の位置が刷新されているようだ。英語だったのが日本語化されていたり、項目が別の位置に移動していたりで大幅には変わっていない気がする。でも、[Facebook API アップデートと付き合う話](http://tech.feedforce.jp/watch-facebook-api-version.html)にあるように、大幅にアップデートされていないか時々確認が必要。

主に参考にしたサイトは

* [Add Facebook Login to Your App or Website](https://developers.facebook.com/docs/facebook-login)
* [facebook OAuth + PHP でログイン処理の実装](http://temog.info/archives/programming/facebook-oauth-php.html)
* [Facebook ログインで認証するまでの手順 (OAuth認証とトークン置換攻撃対策)](http://qiita.com/shimashima/items/6cae3ef84f0b4a12aa9e)
* [FacebookとSNS連携！SDKを使わずにOAuth2でアクセストークン取得する/PHP](http://memories.zal.jp/WP/blog/20150627_2565.html)

やることは、

* facebookアプリの登録
* ログインボタンの設置
* ログインダイアログの呼び出し（認可要求）
* 認可応答＆トークン取得
* ユーザ情報取得
* 自サイトのログイン処理

## facebookアプリの登録

* [Facebook for developers](https://developers.facebook.com/)を開く。
* 右上の[My Apps]を選び、[Add a New App]を選択。
* ウェブサイトを選択
* アプリの名前を入力。日本語でもOK。（スマホサイトの場合、この名前がログインダイアログに出てくるので変な名前にはしないこと。）

* テストバージョン、連絡先メールアドレス、カテゴリを指定する。
![tesrmode.png](https://qiita-image-store.s3.amazonaws.com/0/34699/ff96811c-2e51-3166-8a27-581167e6d3e4.png)

1. テストバージョンか確認があるが、公開する場合は「いいえ」にする。
Facebookのテストユーザを使用したければ「はい」を選択。本番リリース前にテストバージョンを解除するのを忘れないこと。
2. カテゴリを適切なものに設定。（ログイン機能用だとどれでも変わらない？）
3. アプリIDを作成を押す

* セキュリティチェックが出てくるので適切な画像を選ぶ。
＊ 使用するプロダクトを選ぶ。Facebookログインを選ぶで良いか。
　設定画面が新しくなったので、ここはちゃんと確認できていない。

![setup.png](https://qiita-image-store.s3.amazonaws.com/0/34699/b9b50267-65bf-6f09-f035-8ebddff7a284.png)

* SDK for JavaScriptが出てくるが、今回は使わない。
* [Tell us about your website]にサイトのURLを入れる。
* [Next Steps]で[Login]をのアイコンを選ぶ。
* ヘルプページが開く、元のページにはFinishedが表示される。
* 右上の[Skip Quick Start]を押すか、[My Apps]のドロップダウンで、先ほど作ったアプリを選択する。
* ダッシュボードが表示される。
* 左側の「設定(Settings)」を選ぶ。

![設定.png](https://qiita-image-store.s3.amazonaws.com/0/34699/b57ed13f-74d6-7734-8893-f4b3f1a816d6.png)

* [Display Name]で先ほど作った名前が気に入らなければ変える。
* [アプリドメイン(App Domains)]に対象サイトのドメインを指定する、複数のサイト（テストサイト）で使う場合は、それぞれのドメインを入れる。IPアドレスだとダメみたいなので、テスト系などでドメインがない場合は、hostsを設定しておく。例：www.examle.com,dev.example.com
* [連絡先メールアドレス(Contact Email)]にメールアドレスを入れる。何かあったらここに通知が来る？
* [変更を保存(Save Changes)]で保存。
* ここまででも良いが、[詳細設定(Advanced)]タブを選んで、細かく設定できる。
　設定画面が新しくなっているので未確認部分あり。
![oauthd.png](https://qiita-image-store.s3.amazonaws.com/0/34699/55f7f7c7-2a77-c816-46f3-06b2e59cb01e.png)
* [Valid OAuth redirect URIs]にログイン認証に戻ってくるURLを入れる。設定しなくても良いが、設定した方が安全。
* その他の項目は必要に応じて設定する。
* [Save Changes]で保存
* 左側の[App Review]を選ぶ。
![apppreview.png](https://qiita-image-store.s3.amazonaws.com/0/34699/52f84dcc-3fdc-eafc-54fc-27060d9face7.png)
* Make NewApp1 public?を[Yes]にする。[Contact Email]が登録されていないと、[Yes]に出来ない。[No]のままだと、adminか、testuserでしか試せない。実際のFacebookアカウントで試すには[Yes]にする。
* 左側の[App Details]を選ぶ。
![appdetail1.png](https://qiita-image-store.s3.amazonaws.com/0/34699/6e9bfde9-ec18-14d2-5323-c62486000873.png)

* [Icons]の[App Icon]で1024 x 1024ピクセルのアイコンをアップする。なくても良いが、スマホの場合、ログインダイアログでこのアイコンが表示される。
* 左側の[Roles]を選ぶ。
* ここでテストユーザを作ることができる。うっかりアプリをPublicにするのを忘れたりする可能性もあるので、本物のFacebookアカウントでも試す。

## ログインボタンの設置
[公式サイト](https://developers.facebook.com/docs/facebook-login/web/login-button
)にLogin Buttonを生成するページがあるが、このログインボタンを使っているサイトは見当たらなかった。それぞれ自前でログインボタンを作っているようだ。

SDKは使わないので、ログインボタンを設置するので、
facebookのログインダイアログを呼び出すページにリンクする。

```
<a href="/login/facebook_request_auth">Facebookでログイン</a>
```

## ログインダイアログの呼び出し（認可要求）

ログインダイアログの呼び出しページにアクセスすると、
[Invoking the login dialog]
(https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow)で指定されているURLにリダイレクトされるように設定する。

ここの設定がサイトによって違った。15サイトぐらいをChromeの開発者ツールを使ってどんなURLが呼び出されているか見てみたがマチマチだった。

最低限必要なものはclient_idとredirect_uriだけだが、CSRF対策などを考えるとstateも設定したいところ。

```
https://www.facebook.com/dialog/oauth?
  client_id={app-id}
  &redirect_uri={redirect-uri}
　&state={******************************}
  
```

指定できる項目は、主に下記の通り

* client_id（必須）
>アプリで発行されたApp ID

* redirect_uri（必須）
>fsacebookログイン完了後、戻ってくるURLを指定
アプリの登録時に[Valid OAuth redirect URIs]が指定されていれば、指定されているURLしか受け付けない。
``http://example.com/bbb?idx=xxxxxx``のように可変のURLを指定したい場合は、[Valid OAuth redirect URIs]が使えない。セッション等、別の手段で可変の値は持ってた方が良いかも。

* state
>[CSRF攻撃](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B5%E3%82%A4%E3%83%88%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%AA)対策用の値を指定する。
セッションIDをHashした値を使用するなど。ここらへんを参考。
[OAuth 2.0のstateとredirect_uriとOpenID ConnectのnonceとID Tokenについて](http://www.tokumaru.org/d/20110127.html)
大抵CSRF対策のトークンを作る仕組みはプログラムやライブラリに用意されているのでそれを使うのが良いか。


* response_type
>認証後に戻ってくる際に欲しいパラメータを指定する。特に何も指定しなくても、codeが戻ってくる。
code、token、code%20token、granted_scopesが選べる。
WEBサイトのログイン機能であれば、未指定またはcodeで良いか。
[ログインダイアログの呼び出しとリダイレクトURLの設定
](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow?locale=ja_JP)

* scope
> ログイン完了後、Facebookの個人情報から取得したい値をカンマ区切りで指定。
ログイン機能に使うだけであれば未指定で良いか。
[Permissions Reference - Facebook Login](https://developers.facebook.com/docs/facebook-login/permissions)
emailを指定しているサイトが多かった。


## 認可応答＆トークン取得

facebookログイン完了したら、redirect_uriで指定したページに戻ってくる。
その際、codeパラメータ下記のようについて返ってくる。

```
YOUR_REDIRECT_URI?
  code=***************
```

ログインをキャンセルした場合は、下記のようにエラー情報がついて返ってくる。

```
YOUR_REDIRECT_URI?
  error_reason=user_denied
  &error=access_denied
  &error_description=The+user+denied+your+request.
```

この取得できた「code」をつかって下記のURLにHTTPリクエストする。
PHPだったらfile_get_contentsとか。

```
GET https://graph.facebook.com/v2.3/oauth/access_token?
    client_id={app-id}
   &redirect_uri={redirect-uri}
   &client_secret={app-secret}
   &code={code-parameter}
```

指定できる項目は、主に下記の通り

* client_id（必須）
>アプリで発行されたApp ID

* redirect_uri（必須）
> 先ほど指定したのと同じURL。

* client_secret（必須）
> アプリで発行されたApp Secret

* code（必須）
> 受け取ったcodeパラメータ。


正しく処理できていれば、下記のJSONが返ってくる。

```
{
  "access_token": {access-token}, 
  "token_type": 	{type},
  "expires_in":	{seconds-til-expiration}
}
```
このaccess_tokenをユーザ情報を取得するのに使う。


## ユーザ情報取得

access_tokenを用いて、ユーザ情報を取得

```
GET https://graph.facebook.com/me?
     &access_token={access_token}
```

* access_token（必須）
> 先ほど取得したaccess_token

```
{
  "id":"101540562372987329832845483",
  "email":"example@example.com",
  "first_name":"Bob",
  [ ... ]
}
```

ここで得られる、id値はclient_idによって異なる。
アプリを作り直してclient_idが変わってしまったら、以前取得したid値は識別に使えなくなる。


## 自サイトのログイン処理
Facebookから受け取ったidを自サイトのユーザIDと紐付け。
次回のログインの時、idが一致したらログインOKとする。
下記のような流れにしたい。

![Flowchart.png](https://qiita-image-store.s3.amazonaws.com/0/34699/37bfd685-2309-1a9b-1991-1458d848b9f0.png)