Index
初めに
ソーシャルログインが最低限動く環境を作ってみました。
シンプルな造りなのでソーシャルログインについての概要は理解しやすいかと思います。
でも、セキュリティを担保できるほどの試験はしてないし、
実際に商用でのサービス提供もしていないので、これをそのまま商用で使うのはちょっと心許ないです。
あと、商用で使うならGCP側の設定を商用向けの設定にしないといけないっぽいです。
ちなみにfacebookアカウントを使ったソーシャルログイン環境の構築手順もまとめてますので、
興味のある方はそちらもどうぞ。
必要条件
- googleアカウント(事前に作成済である前提としています。)
- GCP環境が利用できる状態。
- phpが動作するウェブサーバ環境(本記事ではXAMPPでlocalhost環境を使ってますが、LAMPでも動きます。)
※XAMPP環境の構築をする場合はこちらを参考にしてください。
全体概要図
構築手順
連携アプリ側の設定
- Apacheの制御下にあるディレレクトリ(phpが動作する環境)へ、以下の様にファイル配置していきます。
C:\xampp\htdocs (←Apacheの制御下のフォルダ、環境の設定によって変わる)
└ social-login
└ google
├ index.php
├ common.php
├ redirect.php
└ btn_google_signin_dark_normal_web.png
btn_google_signin_dark_normal_web.pngは、Googleのページからダウンロードしてきたボタン画像のファイルです。ファイルをダウンロードして解凍してそのまま配置してます。
- index.phpはログインのためのボタンを表示してるだけのシンプルな画面のphpソースコードです。
<html>
<head>
<meta charset="utf-8">
<title>Demo sign in with Google</title>
</head>
<body>
<?php
require_once __DIR__."/common.php";
$url = "https://accounts.google.com/o/oauth2/auth";
$param = http_build_query(array(
"client_id" => $clientId,
"redirect_uri" => $redirectUri,
"scope" => "openid email",
"response_type" => "code"
));
echo '<a href="' . $url . "?" . $param .
'"><img src="btn_google_signin_dark_normal_web.png" alt="googleIcon" title="googleIcon"></a>';
?>
</body>
</html>
- common.phpはこのデモアプリ全体で使用するパラメータの設定と、使用頻度の高いbase64のデコード/エンコード関数を定義するためのファイルです。
後ほど、Google側の設定をした後に、ここの $clientId、$clientSecret、$redirectUri の値を設定します。
<?php
$clientId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com";
$clientSecret = "XXXXXXXXXXXXXXXXXXXXXXXX";
$redirectUri = "https://localhost/social-login/google/redirect.php";
function webSafeBase64encode($data){
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
function webSafeBase64decode($data){
return base64_decode(strtr($data, '-_', '+/') . str_repeat('=', 3 - (3 + strlen($data)) % 4));
}
- redirect.phpは、受け取った認可コードを使ってgoogleにアクセストークンやIDトークン(ユーザ情報含む)をリクエストしてます。また、受け取った情報を検証したりもしています。
<?php
require_once __DIR__ . "/common.php";
if (!array_key_exists("code", $_GET)) {
echo "<hr>Error : incorrect parameter";
return;
}
$param = http_build_query(array(
"code" => $_GET["code"],
"client_id" => $clientId,
"client_secret" => $clientSecret,
"redirect_uri" => $redirectUri,
"grant_type" => "authorization_code"
));
$context = array(
"http" => array(
"method" => "POST",
"header" => "Content-Type: application/x-www-form-urlencoded\r\nContent-Length: " . strlen($param),
"content" => $param
)
);
//Googleにリクエスト
$url = "https://accounts.google.com/o/oauth2/token";
$resp = json_decode(file_get_contents($url, false, stream_context_create($context)));
echo "<h3>*** Response ***</h3>";
echo var_export($resp, true);
//id token(JWT [ヘッダー.ペイロード.署名])をピリオドで分離
$jwt = explode(".", $resp->id_token);
//ヘッダー部分を解析
$header = json_decode(webSafeBase64decode($jwt[0]));
echo "<hr><h3>*** Header ***</h3>";
echo var_export($header, true);
if (strcasecmp($header->typ, "JWT")!== 0) {
//JWT形式のみ対応とします。
echo "<hr>Error : Unsupported format";
return;
}
$alg = "";
switch ($header->alg) {
case "RS256"://RSA PKCS#1 signature with SHA-256
case "PS256"://RSA PSS signature with SHA-256
$alg = OPENSSL_ALGO_SHA256;
break;
case "RS384"://RSA PKCS#1 signature with SHA-384
case "PS384"://RSA PSS signature with SHA-384
$alg = OPENSSL_ALGO_SHA384;
break;
case "RS512"://RSA PKCS#1 signature with SHA-512
case "PS512"://RSA PSS signature with SHA-512
$alg = OPENSSL_ALGO_SHA512;
break;
default:
echo "<hr>Error : Unsupported algorithm";
return;
}
//公開鍵をダウンロード
$certs = (array)json_decode(file_get_contents("https://www.googleapis.com/oauth2/v1/certs"));
foreach ($certs as $keyId => $pem) {
if (strcasecmp($keyId, $header->kid) === 0) {
//署名検証
$verifyed = openssl_verify($jwt[0] . "." . $jwt[1], webSafeBase64decode($jwt[2]), $pem , $alg);
if ($verifyed == 1) {
break;
}else{
echo "<hr>Error : Signature Verification Failed " . openssl_error_string();
return;
}
}
}
//ペイロード部分を解析
$payload = json_decode(webSafeBase64decode($jwt[1]));
echo "<hr><h3>*** Payload ***</h3>";
echo var_export($payload, true);
//RFC7519で定義されている必須パラメータが有るかどうかをチェック
if (
!property_exists($payload, "iss") ||
!property_exists($payload, "sub") ||
!property_exists($payload, "aud") ||
!property_exists($payload, "exp") ||
!property_exists($payload, "iat")
) {
echo "<hr>Error : Invalid Param Received";
return;
}
//レスポンス発行者をチェック
if (
$payload->iss !== "https://accounts.google.com" &&
$payload->iss !== "accounts.google.com"
) {
echo "<hr>Error : Invalid Issure";
return;
}
//今回の使い方だとクライアントIDは一致する。使い方によっては一致しない場合もあるらしい。
if (
$payload->azp !== $clientId ||
$payload->aud !== $clientId
){
echo "<hr>Error : Mismatch Client Id";
return;
}
//access token のhash値を検証する。
$hashedAccessToken = hash("sha256",$resp->access_token);
$hexHash = bin2hex(webSafeBase64decode($payload->at_hash));
if (substr($hashedAccessToken, 0, strlen($hexHash)) !== $hexHash){
echo "<hr>Error : Invalid Access token";
return;
}
//ユーザのEメールアドレスが検証済かどうかをチェックする
if (!$payload->email_verified){
echo "<hr>Error : Not Email Verified";
return;
}
//発行日時をチェックする。前後5分くらいのズレを許容するくらいで十分だと思う。
$now = time();
if ((int) $payload->iat < $now - 5 * 60 || $now + 5 * 60 < (int) $payload->iat) {
echo "<hr>Error : Issued at the wrong time";
return;
}
//ユーザ情報等を画面上に表示する
echo "<hr><h3>*** ユーザ情報等 ***</h3>";
echo "<p>■sub(ユーザの識別子)<br />{$payload->sub}</p>";
echo "<p>■email(ユーザのEメールアドレス)<br />{$payload->email}</p>";
date_default_timezone_set('Asia/Tokyo');
echo "<p>■iat(ID token の発行日時)<br />UnixTime : {$payload->iat} (" .date('Y/m/d H:i:s', $payload->iat) .")</p>";
echo "<p>■exp(ID token の有効期限日時)<br />UnixTime : {$payload->exp} (" .date('Y/m/d H:i:s', $payload->exp) .")</p>";
//認証がOKなら、必要に応じてこれらのパラメータを利用してログイン処理を実装してください。
/**実装例**
$_SESSION['userId'] = $payload->sub;
$_SESSION['email'] = $payload->email;
*/
- これらのファイルを配置し終わったらhttps://localhost/social-login/google/index.php(ここではlocalhost環境で動かしてます)にアクセスしてみてください。
以下の様にボタンが1つだけ表示されたページが閲覧できればOKです。
Google側の設定
OAuth同意画面の設定
-
GCPのコンソールにアクセスして、
メニューから[APIとサービス]>[OAuth同意画面]を選択します。
※既に作成済なら、この[OAuth同意画面の設定]の手順は不要です。読み飛ばして認証情報の設定へと進んでください。
-
とりあえず必須項目だけ埋めてしまいます。その他の項目は必要に応じて設定してください。
設定したら[保存して次へ]をクリック。
-
[スコープを追加または削除]をクリックして、選択肢から「opneid」を追加します。
設定したら[保存して次へ]をクリック。
認証情報の設定
-
GCPのコンソールにアクセスして、
メニューから[APIとサービス]>[認証情報]を選択します。
※OAuth同意画面の設定が未設定の場合は先にそちらを設定してください。
-
"OAuthクライアントID"で、認証情報を作成します。
※作成初回時は画面のUIが本記事記載の内容とはちょっと違うかもしれません。
- [アプリケーションの種類]は[ウェブアプリケーション]を選択します。
- 詳細を設定していきます
- "名前"欄に適当な名前を設定します。
※ここでは「DemoClient」としています。 - "承認済みリダイレクトURI"にredirect.phpへのURIアドレスを設定します。
※ここではlocalhostの環境に合わせてhttps://localhost/social-login/google/redirect.phpと設定しています。
- 認証情報が作成できました。
作成完了時にクライアントIDとクライアントシークレットが表示されますので、
これらの値をcommon.phpの変数である$clientId、$clientSecretに設定します。
※この画面を見逃しても、こららの値は後から確認することできます。クライアントシークレットはリセットもできます。
- 一覧に作成した認証情報が追加されてます。
動かしてみる
- XAMPPを起動する(既に起動している場合は不要)
-
https://localhost/social-login/google/index.phpにウェブブラウザでアクセスする。
- [Sign in with Google]のアイコンをクリックする。
- Googleの認証画面に遷移するので、認証する。
- 認証が成功するとhttps://localhost/social-login/google/redirect.phpにリダイレクトされる。このページではユーザ情報などを表示しています。