(※この記事は 別媒体に投稿した記事 のバックアップです。 canonical も設定しています)
2020-11-22
※この記事は別アカウント(hyiromori)から引っ越しました
社内ツールに認証を入れる際に、Google OAuth を使った認証の仕組みをいれました。
その作業の記録です。
CakePHP 4.1.6 を使っていますが、基本的な部分は CakePHP に限らず PHP 全般で使えると思います。
また最近OAuthなど認証の学習を始めたので、違う箇所や別の良い方法がある場合はコメントいただけるとありがたいです。
Authorization Code Grant によるフローで実装しています今回は Google OAuth を使ってユーザーを認証することを目的としていました。
またGoogleアカウントに紐づくメールアドレス、名前、プロフィール画像を取得してアカウント情報を作ることもしました。
まずGoogle が提供しているライブラリを導入しました。
googleapis/google-api-php-client: A PHP client library for accessing Google APIs
このドキュメントに紹介されています。
Using OAuth 2.0 for Web Server Applications | Google Identity Platform
composer で一発でした。
composer require google/apiclient:"^2.7"
GCPで認証情報を作成すると、JSONファイルで認証情報をダウンロードできます。
今回はJSON文字列ごと環境変数に設定して、それをパースして使用する方法にしました。
# こんな感じで設定されているイメージです
$ export GOOGLE_AUTH_CONFIG='{"web":"...(略)..."}'
// こんな感じで環境変数をパースして使用します。
$config = json_decode(env('GOOGLE_AUTH_CONFIG', '{}'), true);
※以降のサンプルコード内の $config という変数は、このパースした認証情報を指します
APIクライアントの生成は、以下のように実行します。
$client = new Client();
// 上記のパースした認証情報をセットする
$client->setAuthConfig($config);
// リダイレクト用のURLをセットする
// GCPで設定したものと同じである必要がある
$client->setRedirectUri('http://localhost:8080/callback');
※以降のサンプルコード内の $client という変数は、このAPIクライアントを指します
次に認証関連のエンドポイントを用意しました。
必要になるエンドポイントは3つです。
サインイン画面を表示するためのエンドポイントです。
特に処理とかはなく、次に出てくる /request_authorize へのリンクを設置するだけです。
こんな感じで Sign in with Google のリンクを押して、リンクは /request_authorize を指定しています。

(アイコンは以下のリンクにあります)
ログインにおけるブランドの取り扱いガイドライン | Google Identity Platform | Google Developers
Google の OAuth エンドポイントへリダイレクトするエンドポイントです。
色々とパラメーターを付与してリダイレクトする必要があります。
リダイレクトする際に、以下の情報をクエリパラメーターに追加しておきます。
client_idredirect_uri/callback のエンドポイントをドメインなども含め全て指response_typecode をセットAuthorization Code Grant のフローを使うためscopeopenid email profile をセットaccess_typeoffline をセットstate/callback で検証する時に使うCakePHP のコントローラーはこんな感じで実装しました。
public function requestAuthorize()
{
// state の生成
$state = base64_encode(random_bytes(16));
$query = http_build_query([
'client_id' => $config['web']['client_id'], // 認証情報からクライアントIDを取得
'redirect_uri' => 'http://localhost:8080/callback', // GCPで設定したものと同じである必要がある
'response_type' => 'code',
'scope' => 'openid email profile',
'access_type' => 'offline',
'state' => $state
]);
// callback で state のチェックをするためにセッションにセットしておく
$this->getRequest()->getSession()->write('oauth_state', $state);
// Google のエンドポイントへリダイレクト
$this->redirect("https://accounts.google.com/o/oauth2/v2/auth?{$query}");
}
state は CSRF を防ぐために使用するものです。
以下の記事がとても分かりやすかったです。
OAuthやOpenID Connectで使われるstateパラメーターについて | SIOS Tech. Lab
/callbackGoogleでの認証が終わった後に呼ばれるエンドポイントです。
Gクエリパラメーターに必要なパラメーターが付与されているので、それらを処理して認証を完了します。
このエンドポイントはやることが多いので、軽く流れを説明します。
state のチェックし一致しない場合は処理を終了する以下2つのパラメーターが付与されているので取得します。
codestate /request_authorize で渡した state と同じ値がセットされていますクエリパラメーターの state と、セッションの state が一致するかを検証します。
これによって、正しいリクエストであることを検証できます。
クエリパラメーターに付与されている code は Google にアクセスして情報を取得するための引換券のようなものです。code を使って、アクセストークンを取得します。
APIクライアントのメソッドを使えば一行でできます。
$code = $this->getRequest()->getQuery('code');
$accessToken = $client->fetchAccessTokenWithAuthCode($code);
アクセストークン内にある id_token というトークンを検証します。
このIDトークンを検証することで、ユーザーの認証が完了します。
こちらもAPIクライアントのメソッドを使えば一行でできます。
$userInfo = $client->verifyIdToken($accessToken['id_token']);
if (!$userInfo) {
return $this->renderError('トークンの検証に失敗しました');
}
ここは各アプリケーションの要件によって実装が変わってきます。
私の場合は、以下のような処理をしました。
参考までに $userInfo から以下のように情報を所得できます。
$email = $userInfo['email'];
$name = $userInfo['name'];
$picture = $userInfo['picture'];
Google OAuth を使うのは、思っていたよりも手軽にできることが分かりました。
とはいえ、何のためにこの処理を書いているのかは分かったほうが良いなー、と思いました。
私は認証認可の学習をしていて、こういった実装をしてみることで結構理解が深まった気がします。
認証認可は結構重要かつ色々なプロダクトで使っていくものなので、しっかり学習していきたいですね。
Google は OAuth と言っているけど、OpenID Connect とは何が違うのかな・・・?
OAuth2 の上に OpenID Connect が定義されている、とは見たけれど、どう違うのかは分かっていない。
この辺は学習を進めてわかったら追記するかもしてません。