PHP8でGoogleログインを実装する【Google API/OAuth2.0/PHP8】
課題
PHP8系プロジェクトにおけるGoogeログインの実装について、ぱっと参照できる答えが欲しい。
キーワード
- Google Cloud Platform
- Google Login(Google Oauth)
- PHP8
手続き
- OAuth2.0を理解
- 専用のSDKをComposerでプロジェクトにインストール
- 下準備
- GCPでプロジェクトを作成(既存プロジェクトでもOK)
- 「OAuth同意画面」のAPIを有効化
- コールバック用のリダイレクトURLなどを登録
- CLIENT_IDやCLIENT_SECRETキーを取得
- 同意リクエストを開始するボタンをHTMLで作成(公式生成機能あり)
- ボタン生成公式ツールベタ書きではなく、PHPで動的に書く場合
- POSTリクエストをバックエンド側で処理してトークンを受信
- SDKからオブジェクトを呼び出して、CLINET_IDを照合
- verifyIdToken()メソッドでペイロードを受信
- ペイロードから必要なデータを取得
すべてデモコードがあるのでとくにむずかしいところはないと思います。
公式による概要はこちら。
OAuth2.0を理解
おもいっきり割愛。
あまり人道的ではない学習方法ですが、攻撃者側の気持ちになると理解が深まります。state
の不備や、code
の管理とか、Refererヘッダ
まわりとか、redirect_uri
検証不足とか。
専用のSDKをComposerでプロジェクトにインストール
composer require google/apiclient
下準備
このあたりの下準備については割愛します。
GCPでプロジェクトを作成
「OAuth同意画面」のAPIを有効化
コールバック用のリダイレクトURLなどを登録
CLIENT_IDやCLIENT_SECRETキーを取得
みなさんのブログ記事がスクリーンショット豊富でわかりやすいです。
同意リクエストを開始するボタンをHTMLで作成(公式生成機能あり)
よくみる「Googeでログイン」みたいなボタンのHTMLを生成できる公式ツールです。使わなくてもぜんぜんつくれますが、いちおうご紹介。
ボタン生成公式ツールベタ書きではなく、PHPで動的に書く場合
環境変数の設定
だいたい.env
とかに環境変数おくと思うのでセットしておきます。
GOOGLE_OAUTH_CLIENT_ID={クライエントID}
GOOGLE_OAUTH_REDIRECT_URL={リダイレクトURL}
環境変数のために、PHP dotenvをインストールします。
composer require vlucas/phpdotenv
HTML側の実装
Googleログインボタン側のHTMLです。わたしはscript
要素をhead
要素のなかに入れたいので、移動させています。async
属性かdefer
属性か迷いどころですが、公式どおりにしておきます。
id
属性がg_id_onload
のdiv
タグが公式でもにありました。data
属性にいろいろデータを入力することで、ボタンが完成します。GCPのプロジェクト側から払い出されたCLIENT_IDや、じぶんのところのアプリのコールバックURLを属性の値にいれます。
<?php
// オートローダー
require_once __DIR__.'/vendor/autoload.php';
// 環境変数のセッティング(先ほどの.envを読み込んで、$_ENVに入れる)
$dotenv = Dotenv\Dotenv::createImmutable();
$dotenv->load();
?>
...
<head>
<script src="https://accounts.google.com/gsi/client" async></script>
</head>
...
<section>
<div id="g_id_onload"
data-client_id="<?= h($_ENV['GOOGLE_OAUTH_CLIENT_ID']) ?>"
data-login_uri="<?= h($_ENV['GOOGLE_OAUTH_REDIRECT_URI']) ?>"
data-auto_prompt="false">
</div>
<div class="g_id_signin"
data-type="standard"
data-size="large"
data-theme="outline"
data-text="sign_in_with"
data-shape="rectangular"
data-logo_alignment="left">
</div>
</section>
ドキュメントはこの部分です。
data属性のリストはこちらにあります。いろいろカスタム効きます。
フロント側はこれだけでOKです。
POSTリクエストをバックエンド側で処理してトークンを受信
PHP用のクライアントライブラリはこちらから。
コードとしてはこうなります。
<?php
/**
・このファイルはGCPプロジェクトの同意画面APIで入力したコールバックURLで指定したURL
・auth/google-oauth-request/のグーグルログインボタンを押したあとのポップアップから遷移
*/
// オートローダー
require_once __DIR__.'/vendor/autoload.php';
// 環境変数のセッティング(先ほどの.envを読み込んで、$_ENVに入れる)
$dotenv = Dotenv\Dotenv::createImmutable();
$dotenv->load();
// $_POSTで送られてくるクレデンシャルを受信して、verifyIdToken()メソッドでGoogleアカウントのデータを取得する
try {
if(!isset($_POST["credential"])) throw new \Exception("credentialが存在しません");
$credential = $_POST["credential"];
$client = new Google_Client(['client_id' => $_ENV['GOOGLE_OAUTH_CLIENT_ID']]);
$payload = $client->verifyIdToken($credential);
$iss = $payload["iss"]; // Issuer
$azp = $payload["azp"]; // Authorized party
$aud = $payload["aud"]; // Audience
$sub = $payload["sub"]; // Subject
$email = $payload["email"]; // Email
$email_verified = $payload["email_verified"]; // Email verified
$nbf = $payload["nbf"]; // Not before
$name = $payload["name"]; // Name
$picture = $payload["picture"]; // Picture
$given_name = $payload["given_name"]; // Given name
$iat = $payload["iat"]; // Issued at
$exp = $payload["exp"]; // Expiration
$jti = $payload["jti"]; // JWT ID
// バリデーション
if ($iss !== 'https://accounts.google.com' && $iss !== 'accounts.google.com') throw new \Exception("Invalid issuer.");
if ($azp !== $_ENV['GOOGLE_OAUTH_CLIENT_ID']) throw new \Exception("Invalid client ID.");
if ($aud !== $_ENV['GOOGLE_OAUTH_CLIENT_ID']) throw new \Exception("Invalid audience.");
if (!$email_verified) throw new \Exception("Invalid email verified.");
if ($nbf > time()) throw new \Exception("Invalid not before.");
if ($iat > time()) throw new \Exception("Invalid issued at.");
if ($exp < time()) throw new \Exception("Invalid expiration.");
// なにかしらの処理
// ログイン処理
} catch (\Exception $e) {
echo $e->getMessage();
}
まとめ
ComposerやSDKのおかげで実装自体は簡単ですが、技術全体を理解するには長い道を歩く必要がありそうです。
Discussion