(※この記事は 別媒体に投稿した記事 のバックアップです。 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_id
redirect_uri
/callback
のエンドポイントをドメインなども含め全て指response_type
code
をセットAuthorization Code Grant
のフローを使うためscope
openid email profile
をセットaccess_type
offline
をセット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
/callback
Googleでの認証が終わった後に呼ばれるエンドポイントです。
Gクエリパラメーターに必要なパラメーターが付与されているので、それらを処理して認証を完了します。
このエンドポイントはやることが多いので、軽く流れを説明します。
state
のチェックし一致しない場合は処理を終了する以下2つのパラメーターが付与されているので取得します。
code
state
/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 が定義されている、とは見たけれど、どう違うのかは分かっていない。
この辺は学習を進めてわかったら追記するかもしてません。