PHP でのユーザーの認証

PHP Bookshelf チュートリアルのこのパートでは、ユーザーのログインフローを作成する方法と、プロフィール情報を使用してユーザー固有の機能を提供する方法を説明します。

Google Identity Platform を使用すると、ユーザーの情報に簡単にアクセスできる一方で、ユーザーのログイン認証情報は Google により安全に管理されます。 OAuth 2.0 により、アプリのすべてのユーザーのログインフローを簡単に提供できるだけでなく、認証済みユーザーの基本的なプロフィール情報にアプリからアクセスすることもできます。

このページは、複数のページから構成されるチュートリアルの一部です。最初から設定手順を確認するには、PHP Bookshelf アプリに移動してください。

ウェブ アプリケーション クライアント ID の作成

ウェブ アプリケーション クライアント ID を使用すると、アプリケーションがユーザーを承認し、ユーザーの代わりにアプリケーションが Google API にアクセスできます。

  1. Google Cloud Platform Console の認証情報セクションに進みます。

  2. [OAuth 同意画面] をクリックします。製品名として「PHP Bookshelf App」と入力します。関連するオプション フィールドに値を入力します。[保存] をクリックします。

  3. [認証情報を作成] > [OAuth クライアント ID] をクリックします。

  4. [アプリケーションの種類] で [ウェブ アプリケーション] を選択します。

  5. [名前] に「PHP Bookshelf Client」と入力します。

  6. [承認済みのリダイレクト URI] で、次の URL を 1 つずつ入力します。[YOUR_PROJECT_ID] は実際のプロジェクト ID で置き換えます。

    http://localhost:8000/login/callback
    http://[YOUR_PROJECT_ID].appspot.com/login/callback
    https://[YOUR_PROJECT_ID].appspot.com/login/callback
    http://[YOUR_PROJECT_ID].appspot-preview.com/login/callback
    https://[YOUR_PROJECT_ID].appspot-preview.com/login/callback

  7. [作成] をクリックします。

  8. クライアント IDクライアント シークレットをコピーし、後で使用するために保存します。

設定の構成

このチュートリアルの Cloud Storage で説明した settings.yml ファイルを getting-started-php/4-auth ディレクトリにコピーします。コピーしたファイルで、YOUR_CLIENT_IDYOUR_CLIENT_SECRET を、作成済みのクライアント ID とクライアント シークレットに置き換えます。

依存関係のインストール

4-auth ディレクトリで、次のコマンドを入力します。

composer install

ローカルマシンでのアプリの実行

  1. ローカル ウェブサーバーを起動します。

    php -S localhost:8000 -t web
    
  2. ウェブブラウザで、次のアドレスを入力します。

    http://localhost:8000

これで、アプリのウェブページを閲覧できるようになります。Google アカウントでログインして書籍を追加できます。追加した書籍は、上部のナビゲーション バーにある [My Books] リンクから確認できます。

アプリの App Engine フレキシブル環境へのデプロイ

  1. サンプルアプリをデプロイします。

    gcloud app deploy
    
  2. ウェブブラウザで、次のアドレスを入力します。[YOUR_PROJECT_ID] は実際のプロジェクト ID で置き換えます。

    https://[YOUR_PROJECT_ID].appspot.com
    

    アプリを更新する場合は、最初にデプロイしたときと同じコマンドを使って、更新バージョンをデプロイできます。デプロイを行うと、アプリの新しいバージョンが作成され、それがデフォルトのバージョンに設定されます。古いバージョンはそのまま残り、関連付けられた VM インスタンスも同様に残ります。すべてのアプリ バージョンと VM インスタンスが課金対象のリソースとなるのでご注意ください。

    アプリのデフォルト以外のバージョンを削除することで、コストを削減できます。

    アプリのバージョンを削除する手順は次のとおりです。

    1. GCP Console の [App Engine のバージョン] ページに移動します。

      [バージョン] ページに移動

    2. 削除したい、デフォルト以外のアプリのバージョンの横にあるチェックボックスをクリックします。
    3. ページの上部にある [削除] をクリックし、アプリのバージョンを削除します。

    課金対象のリソースをクリーンアップする方法の詳細については、このチュートリアルの最後のステップにあるクリーンアップ セクションを参照してください。

    アプリケーションの構造

    次の図は、アプリケーションを構成するコンポーネントと、それらの接続関係を示しています。

    Auth サンプルの構造

    コードの説明

    PHP セッション

    このチュートリアルでは、PHP ネイティブのセッション管理と memcache を使用してユーザー セッションを維持しています。PHP ランタイムのデフォルトでは、セッションの設定に memcache を使用します。これにより、Silex SessionServiceProvider を使用してユーザーデータを管理できます。

    ユーザーは自分の Google アカウントでログインすると、基本的なユーザーデータがセッションで保存されます。

    $app->register(new SessionServiceProvider);
    // fall back on PHP's "session.save_handler" for session storage
    $app['session.storage.handler'] = null;
    $app['user'] = function ($app) {
        /** @var Symfony\Component\HttpFoundation\Session\Session $session */
        $session = $app['session'];
    
        return $session->has('user') ? $session->get('user') : null;
    };

    PHP 用の Google API クライアント ライブラリ

    Google API PHP クライアントの OAuth 機能を使用すると、Google アカウントでユーザー認証を行うことができます。この機能を利用するには、Google\Client サービスクラスを作成し、アプリケーション用に設定する必要があります。

    $app['google_client'] = function ($app) {
        /** @var Symfony\Component\Routing\Generator\UrlGenerator $urlGen */
        $urlGen = $app['url_generator'];
        $redirectUri = $urlGen->generate('login_callback', [], $urlGen::ABSOLUTE_URL);
        return new Google_Client([
            'client_id'     => $app['config']['google_client_id'],
            'client_secret' => $app['config']['google_client_secret'],
            'redirect_uri'  => $redirectUri,
        ]);
    };

    ユーザーが [Log in] をクリックすると、GET '/login' アクションは Google API PHP クライアント サービスクラスを使用して認証 URL を生成し、リクエストをリダイレクトします。

    $app->get('/login', function () use ($app) {
        /** @var Google_Client $client */
        $client = $app['google_client'];
    
        $scopes = [ \Google_Service_Oauth2::USERINFO_PROFILE ];
        $authUrl = $client->createAuthUrl($scopes);
    
        return $app->redirect($authUrl);
    })->bind('login');

    ユーザーが [Allow] をクリックすると、Google\Client サービスクラスに渡した redirect_uri が呼び出されます。このアクションが受信した認証コードを使用して、アクセス トークンを取得します。取得したトークンデータがセッション内で保存され、ユーザーはホームページにリダイレクトされます。

    $app->get('/login/callback', function () use ($app) {
        /** @var Request $request */
        $request = $app['request'];
    
        if (!$code = $request->query->get('code')) {
            return new Response('Code required', Response::HTTP_BAD_REQUEST);
        }
    
        /** @var Google_Client $client */
        $client = $app['google_client'];
        $authResponse = $client->fetchAccessTokenWithAuthCode($code);
    
        if ($client->getAccessToken()) {
            $userInfo = $client->verifyIdToken();
    
            /** @var Symfony\Component\HttpFoundation\Session\Session $session */
            $session = $app['session'];
            $session->set('user', [
                'id'      => $userInfo['sub'],
                'name'    => $userInfo['name'],
                'picture' => $userInfo['picture'],
            ]);
    
            return new Response('', Response::HTTP_FOUND, ['Location' => '/']);
        }
    
        // an error occured while trying to authorize - display it
        return new Response($authResponse['error_description'], 400);
    })->bind('login_callback');

    ユーザーがログアウトすると、'/logout' アクションがユーザー情報をセッションから消去します。

    $app->get('/logout', function () use ($app) {
        /** @var Symfony\Component\HttpFoundation\Session\Session $session */
        $session = $app['session'];
        $session->remove('user');
    
        return new Response('', Response::HTTP_FOUND, ['Location' => '/']);
    })->bind('logout');

    ユーザーが認証されると、名前や写真など、ユーザーに関する有益な情報が使用できます。ユーザーがログインした場合に、これらの情報を基本テンプレートに表示できます。ユーザー情報がない場合には、[Login] ボタンを表示します。

    {% if app.user %}
      {% if app.user.picture %}
        <img src="{{ app.user.picture }}" class="img-circle" width="24" alt="Photo" />
      {% endif %}
      <span>
        {{ app.user.name }} &nbsp;
        <a href="/logout">(logout)</a>
      </span>
    {% else %}
      <a href="/login">Login</a>
    {% endif %}
このページは役立ちましたか?評価をお願いいたします。