【PHP】Gmail認証 - ログインと認証チェック -

Gmailを使ってGoogle認証APIでログインを実装する方法、またログイン後の認証チェックを実装してみます。

前提としてGoogle APIsコンソール(以下URL)からサービスキーを発行します。

https://console.developers.google.com/apis/dashboard

※なお今回FuelPHPを使って実装していますが、フレームワークの特別な機能は特に使用していません。

事前準備

サービスキー発行

  1. Google APIsコンソールへサインイン

    https://console.developers.google.com/?hl=JA

  2. Google APIsコンソールの左側メニュー → 認証情報 → 認証情報を作成 → OAuth クライアント ID

  3. 必要項目を入力

    名前 任意のアプリケーション名を入力
    承認済みの JavaScript 生成元 今回は使用しません
    承認済みのリダイレクト URI 認証後にGoogleからコールバックされるエンドポイント
  4. 保存ボタンで client_id と client_secret が発行される

※OAuth同意画面でアプリケーションのスコープを指定することができます。これを内部にすると、組織内のGmailアカウントに限定されるので、社内システムなどの開発で使用する際はではチェックすると良いです。

ログイン処理

Google Api Key設定ファイル

fuel/app/config/test/apikeys.php

事前準備で発行したclient_idとclient_secretを設定ファイルへ定義します。

<?php
/**
 * Google API Keys
 */
return array(
    'client_id' => '00000000-xxxxxxxx.apps.googleusercontent.com',
    'client_secret' => 'xxxxxxxx',
);

※環境毎に違うはずなので config/{各環境}/apikeys.php のように分けておくと良いです。

Google Apis設定ファイル

fuel/app/config/googleapis.php

<?php
/**
 * Google APIs
 */
return array(
    'auth' => array(
        'auth'     => 'https://accounts.google.com/o/oauth2/auth',
        'token'    => 'https://accounts.google.com/o/oauth2/token',
        'userinfo' => 'https://www.googleapis.com/oauth2/v1/userinfo',
        'profile'  => 'https://www.googleapis.com/auth/userinfo.profile email',
    ),  
);

ログインコントローラ

fuel/app/class/controller/login.php

<?php
/**
 * ログインコントローラ
 * @access public
 */
class Controller_Login extends Controller
{
    /** 
     * ログインアクション
     *
     * ブラウザで始めにリクエストするアクション
     * client_idを使ってGoogleの認証APIへリダイレクトする。
     * パラメータに指定する redirect_uri はGoogleから認証後に(合否問わず)
     * アプリケーション側にコールバックされるアクションを指す。
     *
     * @access public
     */
    public function action_index() {
        $apikeys    = \Config::load('apikeys', true);
        $googleapis = \Config::load('googleapis', true);
        $authapis   = $googleapis['auth'];

        $querys = array(
            'client_id'     => $apikeys['client_id'],
            'redirect_uri'  => Uri::base().'login/callback',
            'scope'         => $authapis['profile'],
            'response_type' => 'code',
        );

        Response::redirect($authapis['auth'].'?'.http_build_query($querys));
    }

    /**
     * ログインコールバックアクション
     *
     * Gmail認証後に(Googleから)リクエストされるアクション
     * client_secret、およびGETパラメータの code を利用してアクセストークンを取得する。
     * 取得したアクセストークンをクッキーにセットしてログイン完了
     * 成功時は /top/index ページにリダイレクトする。
     *
     * @access public
     */
    public function action_callback() {
        $apikeys    = \Config::load('apikeys', true);
        $googleapis = \Config::load('googleapis', true);
        $authapis   = $googleapis['auth'];

        $validation = Validation::forge();
        $validation->add('code', 'CODE')
            ->add_rule('required')
            ->add_rule('trim')
            ->add_rule('max_length', 128);
        $validation->set_message('required', '不正なリクエストです。');

        if( !$validation->run(array('code' => Input::get('code'),))){
            Session::set_flash('message', implode('<br>', array_values($validation->error_message())));
            Response::redirect('login/error');
        }

        $code = $validation->validated('code');

        // アクセストークンの取得
        $params = http_build_query(array(
            'code'          => $code,
            'grant_type'    => 'authorization_code',
            'redirect_uri'  => Uri::base().'login/callback',
            'client_id'     => $apikeys['client_id'],
            'client_secret' => $apikeys['client_secret'],
        ));

        $header = array(
            "Content-Type: application/x-www-form-urlencoded",
            "Content-Length: ".strlen( $params )
        );

        $options = array('http' => array(
            'method'  => 'POST',
            'header'  => implode( "\r\n", $header ),
            'content' => $params,
        ));

        $response = null;
        try {
            $response = file_get_contents(
                $authapis['token'],
                false,
                stream_context_create( $options )
            );
        } catch( Exception $e ) {
            Session::set_flash('message', 'ログインに失敗しました。');
            Response::redirect('login/error');
        }

        $response_decode = json_decode($response, true);

        if( !isset($response_decode['access_token']) ) {
            Session::set_flash('message', 'ログインに失敗しました。');
            Response::redirect('login/error');
        }

        Cookie::set('access_token', $response_decode['access_token']);
        Response::redirect('/top/index');
    }

    /**
     * 認証エラー
     *
     * ログイン失敗時のアクション
     *
     * @access public
     */
    public function action_error() {
        $message = Session::get_flash('message');

        $data = compact('message');

        return Response::forge(View::forge('login/error', $data), 400);
    }
}

※各アクションについてはヘッダーコメントを参照

動作確認

ブラウザで {ドメイン}/login/index にアクセスすると、以下のようなGmail選択画面へリダイレクトします。

また、スコープを内部にして、外部のGmailからログインしようと試みたときは、以下のようなエラーが発生します。

認証チェック

ここではGmailログインにより払い出したアクセストークンが有効なトークンかどうかのチェックと、アクセストークン に紐づくユーザー情報を取得する部分になります。<.p>

コントローラの規定クラス

fuel/app/class/controller/base.php

<?php
/**
 * 規定クラス
 */
class Controller_Base extends Controller_Template
{
    /**
     * ログインユーザーの情報格納用の配列
     *
     * @var array (
     *   'id'      => string,
     *   'email'   => string,
     *   'name'    => string,
     *   'picture' => string,
     *   'locale'  => string,
     * )
     */
    public $user = array();

    /**
     * コントローラ内の共通処理
     * ・ログインチェック
     *
     * @access public static
     */
    public function before() {
        parent::before();

        $googleapis = \Config::load('googleapis', true);
        $authapis   = $googleapis['auth'];

        // アクセストークンをユーザーのクッキーから取得
        $access_token = Cookie::get('access_token', null);
        if(!$access_token) {
            Response::redirect('login/index');
        }

        $params = array(
            'access_token' => $access_token,
        );

        // アクセストークンに紐付くユーザー情報を取得
        try{
            $response   = file_get_contents($authapis['userinfo'].'?'.http_build_query($params));
            $user_info  = json_decode($response, true);

            $this->user = array(
                'id'      => $user_info['id'],
                'email'   => $user_info['email'],
                'name'    => $user_info['name'],
                'picture' => $user_info['picture'],
                'locale'  => $user_info['locale'],
            );
        } catch(Exception $e) {
            Response::redirect('login/index');
        }
    }
}

ユーザー情報

上記処理で認証チェックをパスしコントローラ側に処理が渡ると、 $this->user には以下のような値が入ります。

array(5) {
  ["id"]=>
  string(21) "012345678901234567890"
  ["email"]=>
  string(14) "test@gmail.com"
  ["name"]=>
  string(9) "Googleアカウントの名前"
  ["picture"]=>
  string(92) "https://lh4.googleusercontent.com/-000000000/AAAAAAAAAAI/AAAAAAAAAAc/xxxxx/photo.jpg"
  ["locale"]=>
  string(2) "ja"
}

※idはGoogle側で払い出される一意なアカウントIDです。

コメント

このブログの人気の投稿

docker-compose up で proxyconnect tcp: dial tcp: lookup proxy.example.com: no such host

docker-compose で起動したweb、MySQLに接続できない事象

【PHP】PHP_CodeSnifferを使う(コーディングルールのカスタマイズ)