facebookでのログインを実装します。実装の難易度はやや簡単です。日本語のドキュメントがあります。
PHPでサーバーサイドのログインを行う場合、公式ドキュメントのログインフローを手作業で構築するを参照します。
各種情報
前提条件
- 言語はPHPで、バージョンは 5.6 を想定
- フレームワークに CodeIgniter 3 を使用
- WebサイトのURLは、https://example.com と想定して説明します
参考URL
- facebook for developers https://developers.facebook.com
- CodeIgniterユーザガイド http://codeigniter.jp/user_guide/3/index.html
多用している CodeIgniter のコマンド
redirect(リダイレクト先, リダイレクトメソッド, HTTPレスポンスコード)
リダイレクト先へリダイレクトします。リダイレクト先に ‘login’ と指定していれば、Loginコントローラーのindexメソッドへリダイレクトします(http://example.com/login/index)。素のPHPであれば、header('Location: http://example.com/login/index');
のようになります。
$this->input->get_post('・・・') $this->input->post('・・・')
$_GET
や$_POST
から値を取り出します。その値(項目)がないときは、null
を返してくれます。get_post()
は先に $_GET
を調べて、値がなければ $_POST
を調べてくれます。post()
は $_POST
だけを調べます。
$this->load->library('・・・') $this->load->view('・・・')
ライブラリやビューとして作成した PHP ファイルをインクルードします。require_once '・・・'
と同じ意味です。
random_string('md5')
ランダムな文字列を作成する関数です。'md5'
を指定しているのはちょうど 32 文字の文字列を返してくれるからで、他意はありません。推測されにくいランダムな英数字の文字列であることが重要です。
facebook用ライブラリの作成
facebook ログイン用のライブラリを作成します。処理の大部分はこのライブラリに書いていきます。CodeIgniter でのライブラリは、以下のようにクラスとして作成することになっています。
ログインに必要な情報は、あらかじめ変数(以下のコードでは private メンバー)に入れておきましょう。
Facebook_library.php
<?php
defined('BASEPATH') or exit('No direct script access allowed');
/**
* facebookライブラリー
*/
class Facebook_library
{
/**
* フィールド
*/
private $ci;
// facebookアプリ情報
private $clientId = '00000000000000';
private $appSecret = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
// 登録済みのリダイレクトURL
private $redirectUrl = 'https://example.com/login/facebook';
// エンドポイント
private $apiAuthorizate = 'https://www.facebook.com/v3.0/dialog/oauth?';
private $apiAccessToken = 'https://graph.facebook.com/v3.0/oauth/access_token';
private $apiDebugToken = 'https://graph.facebook.com/v3.0/debug_token';
private $apiGraphBase = 'https://graph.facebook.com/v3.0/';
/**
* コンストラクタ
*/
public function __construct()
{
$this->ci = &get_instance();
// ライブラリを読み込んでおく(require_once と同義)
$this->ci->load->library('curl_library');
}
}
エンドポイントは変わる可能性があるため、公式ドキュメントを参照して、正しく入力してください。
Loginコントローラーの準備
facebookライブラリをあらかじめロードしておきます。
Login.php
public function __construct()
{
parent::__construct();
$this->load->library('line_library');
// 以下の行を追加
$this->load->library('facebook_library');
「facebookでログイン」のボタンが押されたときの処理を追加します。
Login.php
public function index()
{
// 押されたボタンによってログインの処理を分岐
switch ($this->input->post('submit')) {
case 'line':
redirect('login/login_line', 'location', 302);
break;
case 'yahoo':
break;
case 'twitter':
break;
case 'instagram':
break;
case 'facebook':
// 以下の行を追加
// ボタンがクリックされたら login_facebook メソッドへ移動
redirect('login/login_facebook', 'location', 302);
break;
ログイン開始処理の作成(login_facebookメソッド)
Loginコントローラーに、login_facebook メソッドを追加します。このメソッドがログイン処理の最初のステップになります。
Login.php
/**
* facebookによるログインを開始する
*/
public function login_facebook()
{
// 認証エンドポイントを取得し、リダイレクト
$endpoint = $this->facebook_library->getAuthorize();
header('Location: ' . $endpoint);
exit;
}
facebookのログインページ(認証エンドポイント)へリダイレクトするために、そのURLを構築します。
認証エンドポイントのURLを作成する(getAuthorizeメソッド)
facebook_libraryライブラリに、getAuthorize メソッドを作成します。
Facebook_library.php
/**
* 認証エンドポイントのURLを返す
* @return string 認証エンドポイント
*/
public function getAuthorize()
{
$this->ci->load->helper('string');
// クロスサイトリクエストフォージェリ対策(ランダムな文字列を作成する、32バイト以上推奨)
$state = random_string('md5');
$_SESSION['auth'] = $state;
// 送信パラメータ作成
$params = [
'client_id' => $this->clientId,
'redirect_uri' => $this->redirectUrl,
'state' => $state,
'response_type' => 'code',
'scope' => 'public_profile email',
];
$url = $this->apiAuthorizate . http_build_query($params, '', '&');
return $url;
}
認証エンドポイントのURLに、必要なパラメーターを付加します。各パラメーターの内容は下表のとおりです。すべて必須です。
パラメーター | 内容 |
---|---|
client_id | アプリID。アプリをfacebookに登録したとき、アプリに割り当てられたID。 |
redirect_uri | facebookで認証後、リダイレクトで戻ってくるURL。facebookにあらかじめ登録している必要がある。 |
state | CSRF(クロスサイトフォージェリ)防止用のランダムな文字列。32バイト以上が推奨される。 |
response_type | 'code' を指定する。 |
scope | アプリが求める権限。複数の権限は空白で区切る。空白は '+' にエンコードする点がLINEと異なるので注意すること。 |
認証コールバックを処理する(facebookメソッド)
ユーザーがfacebookでログインすると、登録したリダイレクトURLへ戻ってきます。
今回は Login コントローラーの facebook メソッド(https://example.com/login/facebook)へ戻ってくるように設定しているので、facebook メソッドを作成します。
Login.php
/**
* facebookのログイン認証コールバックを処理する
*/
public function facebook()
{
// レスポンスにエラーがあるか(if (isset($_GET['error'])) { $error = $_GET['error']; } と同義)
$error = $this->input->get_post('error');
if (!empty($error)) {
$_SESSION['message'] = 'ログインがキャンセルされたか、その他のエラーが発生しました。エラー内容:' . $error;
redirect('login', 'location', 302);
}
// セッションの state を読み取って、セッションは削除しておく
$savedState = $_SESSION['auth'];
unset($_SESSION['auth']);
// レスポンスのcodeとstateを取得
$code = $this->input->get_post('code');
$state = $this->input->get_post('state');
// stateの確認
if ($savedState !== $state) {
$_SESSION['message'] = '不正なレスポンスです。';
redirect('login', 'location', 302);
}
// アクセストークンをリクエスト
$token = $this->facebook_library->requestAccessToken($code);
if ($token === false) {
$_SESSION['message'] = 'アクセストークンの取得に失敗しました。';
redirect('login', 'location', 302);
}
// アクセストークンの有効性をチェック
$tokenInfo = $this->facebook_library->debugAccessToken($token['access_token']);
if (empty($tokenInfo) || isset($tokenInfo['error']) || ($tokenInfo['data']['is_valid'] === false)) {
$_SESSION['message'] = '有効なアクセストークンではありません。';
redirect('login', 'location', 302);
}
// アクセストークンの有効期限をチェック
if ($tokenInfo['data']['expires_at'] < time()) {
$_SESSION['message'] = 'アクセストークンの有効期限が切れています。';
redirect('login', 'location', 302);
}
// ユーザー属性の取得
$user = $this->facebook_library->requestUser($tokenInfo['data']['user_id'], $token['access_token']);
if ($user === false) {
// ユーザー属性の取得に失敗してもログイン自体は成功しているので、処理を続行する
$user = [];
}
// 結果の表示
$viewdata = [
'message' => 'Facebookでログインしました。',
'data' => [
'access_token' => $token,
'token_info' => $tokenInfo,
'user' => $user,
],
];
$this->load->view('logined', $viewdata);
}
ユーザーがfacebookへのログインをキャンセルすると、リダイレクトURLに ‘error’ のパラメーターが付加されます。
まずはパラメーターに ‘error’ があるかどうか調べます。’error’ がある場合、ログインがキャンセルされたか、それ以外のエラーで認証が失敗したことになるので、ログインページへ戻ります。
‘error’ がなければ、正常なレスポンスかどうかを確認するため state を比較します。
セッションに保管していた state と、URL に含まれている state パラメーターを比較して、同じ文字列であることを確認します。文字列が異なる場合、CSRF の可能性を考慮して処理を中止します。
state が一致していれば、アクセストークンをリクエストします。
アクセストークンのリクエストには、認証コードが必要です。認証コードは ‘code’ というパラメーターに含まれています。
アクセストークンのリクエストは facebook_library ライブラリの requestAccessToken メソッドで行います。このメソッドは後ほど作成します。
アクセストークンを取得できたら、そのアクセストークンが有効かどうかを確認します。
facebook_library ライブラリの debugAccessToken メソッドは、後ほど作成します。
アクセストークンが有効であっても、アクセストークンには期限があります。期限切れでないか確認します。
アクセストークンが有効期限内であれば、ここでやっとログイン成功といえます。
ユーザーに関する情報(ユーザー属性)が必要な場合、ユーザー属性をリクエストします。最初にアプリの権限を指定しましたが、この権限によって、取得できる情報が異なります。
アクセストークンのリクエスト(requestAccessTokenメソッド)
アクセストークンのリクエストは、facebook_library ライブラリの requestAccessToken メソッドで行います。
Facebook_library.php
/**
* アクセストークンをリクエストする
* @param string $code 認証コード
* @return mixed 取得成功:アクセストークンと関連情報の配列 取得失敗:false
*/
public function requestAccessToken($code)
{
// パラメータ作成
$params = [
'client_id' => $this->clientId,
'redirect_uri' => $this->redirectUrl,
'client_secret' => $this->appSecret,
'code' => $code,
];
// リクエスト
$response = $this->ci->curl_library->execGetCurl($this->apiAccessToken, $params);
// レスポンスの確認
if (empty($response) || (!is_array($response))) {
return false;
}
if (isset($response['error'])) {
return false;
}
return $response;
}
アクセストークンのリクエストは、access_tokenエンドポイントへ GET で行います。パラメーターとして、以下の値を付加する必要があります。
パラメーター | 内容 |
---|---|
client_id | アプリID。 |
redirect_uri | 登録済みのリダイレクトURL。認証のために必要で、リダイレクトはされません。 |
client_secret | app secretの文字列。 |
code | 認証コード。facebookからのコールバックに付加されていた code の値。 |
アクセストークンのリクエストに成功した場合、以下の値が配列で返ってきます(実際の戻り値は JSON 文字列だが、execGetCurl メソッドが配列に変換して返す)。
配列の項目 | 内容 |
---|---|
access_token | アクセストークン |
token_type | トークンのタイプ |
expires_in | トークンの有効期間。秒数。 |
アクセストークンの発行に失敗した場合、エラーメッセージが返ってきます。
アクセストークンを検証する(debugAccessTokenメソッド)
取得したアクセストークンが有効かどうかを調べます。debug_token エンドポイントへリクエストすると、トークンに関する情報が返ってきます。
debug_tokenエンドポイントへのリクエストは、facebook_library ライブラリの debugAccessToken メソッドで行います。
Facebook_library.php
/**
* アクセストークンを検証する
* @param string $token アクセストークン
* @return mixed OK:アクセストークン関連情報の配列 NG:配列以外のデータ
*/
public function debugAccessToken($token)
{
// パラメータ作成
$params = [
'input_token' => $token,
'access_token' => $token,
];
// リクエスト
return $this->ci->curl_library->execGetCurl($this->apiDebugToken, $params);
}
debug_token エンドポイントへ送信するパラメーターは以下のとおりです。
パラメーター | 内容 |
---|---|
input_token | 検証するトークン。アクセストークンを検証するので、アクセストークンを指定する。 |
access_token | アクセストークン。 |
debug_token エンドポイントからのレスポンスは JSON(配列)になります。レスポンスに含まれるすべての項目を知るには、公式ドキュメントを参照してください。
親項目 | 子項目 | 内容 |
---|---|---|
data | app_id | アプリID |
application | アプリ名。 | |
expires_at | アクセストークンの有効期限。UNIX時刻。 | |
is_valid | アクセストークンが有効かどうか。Bool値。 | |
issued_at | アクセストークンが発行された日時。UNIX時刻。 | |
metadata | アクセストークンに関連付けられたメタデータ。 | |
scopes | アクセストークンに付与された権限の配列。 | |
user_id | facebookにおけるユーザーID |
レスポンスの is_valid が false の場合、error が返ってきた場合、または想定外のレスポンスが返った来た場合は、アクセストークンが無効と判断します。
ユーザー属性をリクエストする(requestUserメソッド)
アクセストークンを取得できたら、ユーザー属性をリクエストします。
エンドポイントは API のベースにユーザーIDをつなげたものになります。ユーザーIDが 12345 であれば、エンドポイントのURLは https://graph.facebook.com/v3.0/12345
になります。さらにパラメーターとしてアクセストークンを付加し、GET でリクエストします。
ユーザー属性をリクエストする requestUser メソッドを、facebook_library ライブラリに追加します。
/**
* ユーザー属性をリクエストする
* @param string $userId ユーザーID
* @param string $accessToken アクセストークン
* @return mixed 取得成功:ユーザー属性の配列 取得失敗:false
*/
public function requestUser($userId, $accessToken)
{
$url = $this->apiGraphBase . $userId;
$params = [
'access_token' => $accessToken,
];
$response = $this->ci->curl_library->execGetCurl($url, $params);
if (empty($response)) {
return false;
}
return $response;
}
正常にユーザー属性を取得できた場合、JSON形式でデータが返ってきます。どのようなユーザー属性が含まれているかは、公式ドキュメントを参照してください。
以上で facebook のログイン処理は完成です。