SNSを使用したログイン – facebook 実装編

投稿者: | 2018年6月26日

facebookでのログインを実装します。実装の難易度はやや簡単です。日本語のドキュメントがあります。
PHPでサーバーサイドのログインを行う場合、公式ドキュメントのログインフローを手作業で構築するを参照します。

各種情報

前提条件

  • 言語はPHPで、バージョンは 5.6 を想定
  • フレームワークに CodeIgniter 3 を使用
  • WebサイトのURLは、https://example.com と想定して説明します

参考URL

多用している 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_urifacebookで認証後、リダイレクトで戻ってくるURL。facebookにあらかじめ登録している必要がある。
stateCSRF(クロスサイトフォージェリ)防止用のランダムな文字列。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_secretapp 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(配列)になります。レスポンスに含まれるすべての項目を知るには、公式ドキュメントを参照してください。

親項目子項目内容
dataapp_idアプリID
applicationアプリ名。
expires_atアクセストークンの有効期限。UNIX時刻。
is_validアクセストークンが有効かどうか。Bool値。
issued_atアクセストークンが発行された日時。UNIX時刻。
metadataアクセストークンに関連付けられたメタデータ。
scopesアクセストークンに付与された権限の配列。
user_idfacebookにおけるユーザー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 のログイン処理は完成です。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)