WEBページに「Googleアカウントでログイン」を実装する

# 目次
- はじめに
- 目標
- クライアントIDを取得する
- クライアントサイドのコード（JavaScript）
- サーバサイドのコード（PHP)
- 実行してみる
- お世話になったサイト

## はじめに

WEBアプリを作っていて、「Googleアカウントでログイン」が実装できたので備忘録として残しておく。
実装にあたっては、[Googleの公式ドキュメント](https://developers.google.com/identity/sign-in/web/sign-in?authuser=1)を大いに参考にしました。動画も一緒に載っていて、日本人が解説しています。

PHPとJSを使って実装しています。サーバサイドの言語は自分はPHPを使いましたが、公式ドキュメントにはNode.jsとJavaとPythonのコードも載ってました。自分はPHPしか試していません。


#### 必要なもの
- テキストエディタ
- サーバ
- ブラウザ
- Composer
- PHP（Composerを使うので）

## 目標

GoogleアカウントでログインされたユーザのGoogleアカウントのプロフィール情報を取得し、ログイン状態を付与する、というプログラムを書きます。作成するファイルは、以下の通りです。

- Googleアカウントでログインするためのページ(HTML)
- ユーザのプロフィール情報を受け取るための処理をするファイル(PHP)
- ログイン成功後に遷移させるページ(PHP)

表示するページのファイルは2つ、バックエンドの処理を行うファイルが1つ、計3つのファイルを作成します。ここでは最低限しか作っていないので、ユーザのプロフィールを取得していろいろとやりたいことがある場合はこのほかにいろいろ処理を足すことになります。


##クライアントIDを取得する

API使用の際に定番の、クライアントID取得がまずは必要です。GCPから、プロジェクトの作成を行います。
プロジェクトのホームから、APIとサービス>認証情報 を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486031/c333f758-6518-570e-90f4-049dca22f60a.png)

画面左上の「認証情報を作成」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486031/7facc7a8-8324-9e5f-cb2a-9a364d45af2d.png)

作成するのは「OAuthクライアントID」です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486031/c581111e-a8e4-5cef-f4d8-4975c9b98c1b.png)

アプリケーションの種類を「ウェブアプリケーション」と選択し、入力事項を埋めます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486031/00cd86f7-1b21-ccfd-f3c3-6fc315104706.png)

作成が完了するとクライアントIDが発行されます。あとで必要になります。





## クライアントサイドのコード
#### ログインさせるページ
HTMLとJSのみで実装します。クライアントIDを認識させ、Google Platform Libraryを読み込みます。HTMLファイルのheadタグ内に以下のコードをコピペします。

```HTML
<script src="https://apis.google.com/js/platform.js" async defer></script>
<meta name="google-signin-client_id" content="<用意したクライアントID>">
```

bodyタグ内にはGoogleアカウントでログインボタンを設置します。これもGoogleが用意してくれています。

```HTML
<div class="g-signin2" data-onsuccess="onSignIn"></div>
```
getBasicProfile()メソッドを使うことで、ログインしたユーザのプロフィール情報を取得できます。scriptタグ内に以下を記述します。

```JavaScript
function onSignIn(googleUser) {
  var profile = googleUser.getBasicProfile();
  console.log('ID: ' + profile.getId()); // Do not send to your backend! Use an ID token instead.
  console.log('Name: ' + profile.getName());
  console.log('Image URL: ' + profile.getImageUrl());
  console.log('Email: ' + profile.getEmail()); // This is null if the 'email' scope is not present.
}
```

コンソールにユーザのプロフィールが表示されているかと思います。これでユーザの識別がクライアント側でできたことになります。ここで取得しているプロフィール情報はクライアント側でしか持っていません。このGoogleアカウントのプロフィール情報を自サイトにログインするのに使うために、サーバに送信する必要があります。しかし、ここで注意しなければならないことがあります。

- gmailのアドレスを個人の識別に使ってはいけない。代わりにGoogle IDまたはsub（後述）を使う。
- なりすましのリスクがあるため、getId()で得られたGoogle IDを生でサーバに送信してはいけない。代わりにIDトークンを送信する。

ユーザの識別にはIDを使え、だがIDは直接送らず、IDトークンを送れ、ってことです。~~IDトークンはただの文字列です。~~サーバ側でそのトークンをもとにユーザのプロフィール情報を取得することが可能です。
→2020/02/14: コメントでご指摘をいただきました。「ただの文字列」という言い方は良くなかったです。正確には、IDトークンはJWTという形式です。

ってなわけでIDトークンをサーバにPOST送信します。
まずIDトークンを取得します。さっきコピペした関数の中身を変えます。POST送信ができれば別にこの書き方にこだわる必要はありません。個人的にjQueryの$.ajax()のほうがわかりやすくて好きなので自分はそっちで書きましたがうまくいきました。

```JS
function onSignIn(googleUser) {
  var id_token = googleUser.getAuthResponse().id_token;
  var xhr = new XMLHttpRequest();
  xhr.open('POST', 'https://yourbackend.example.com/tokensignin');
  xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  xhr.onload = function() {
    console.log('Signed in as: ' + xhr.responseText);
  };
  xhr.send('idtoken=' + id_token);
}
```


## サーバサイドのコード
POST送信されたIDトークンを受け取り、トークンを照合します。トークンの称号にはGoogle API Client Libraryが必要です。Composerを使ってインストールします。ComposerはPHPでのパッケージ管理ツールで、npm、yarn、gemなどの親戚です。使い方はググってください。

```
composer require google/apiclient
```
POSTでIDトークンを受け取り、照合しています。

```PHP
<?php 
require_once 'vendor/autoload.php';

$id_token = filter_input(INPUT_POST, 'id_token');
define('CLIENT_ID', 'XXXXXX.apps.googleusercontent.com');

$client = new Google_Client(['client_id' => CLIENT_ID]); 
$payload = $client->verifyIdToken($id_token);
if ($payload) {
  $userid = $payload['sub'];
}
```
照合に成功したら、`$payload`にプロフィール情報が入ります。`$payload`の中身は以下のようになってます。

```json
{
 // These six fields are included in all Google ID Tokens.
 "iss": "https://accounts.google.com",
 "sub": "XXXXXX",
 "azp": "XXXXXX.apps.googleusercontent.com",
 "aud": "XXXXXX.apps.googleusercontent.com",
 "iat": "XXXXXX",
 "exp": "XXXXXX",

 // These seven fields are only included when the user has granted the "profile" and
 // "email" OAuth scopes to the application.
 "email": "testuser@gmail.com",
 "email_verified": "true",
 "name" : "Test User",
 "picture": "https://lh4.googleusercontent.com/-kYgzyAWpZzJ/ABCDEFGHI/AAAJKLMNOP/tIXL9Ir44LE/s99-c/photo.jpg",
 "given_name": "Test",
 "family_name": "User",
 "locale": "en"
}
```

ユーザの識別をするためのIDは`$payload['sub']`の値です。gmailのユーザ名じゃないんや～😲

このIDを使ってユーザの識別ができたらセッション変数でも使ってログイン状態を付与し、ログイン後のページに遷移します。


## 実行してみる
```login.html
<html>
<head>
    <script src="https://apis.google.com/js/platform.js" async defer></script>
    <meta name="google-signin-client_id" content="<用意したクライアントID>">
</head>
<body>
    <div class="g-signin2" data-onsuccess="onSignIn"></div>
    <script>
        function onSignIn(googleUser) {
            var id_token = googleUser.getAuthResponse().id_token;
            var xhr = new XMLHttpRequest();
            xhr.open('POST', 'test.php');
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            xhr.onload = function() {
                console.log('Signed in as: ' + xhr.responseText);
            };
            xhr.send('idtoken=' + id_token);
            window.location.href = 'index.php';
        }
    </script>
</body>
</html>

```
```test.php

<?php
session_start();
require_once 'vendor/autoload.php';

$id_token = filter_input(INPUT_POST, 'id_token');
define('CLIENT_ID', '<用意したクライアントID>');

$client = new Google_Client(['client_id' => CLIENT_ID]); 
$payload = $client->verifyIdToken($id_token);
if ($payload) {
    $userid = $payload['sub'];
}

//DBなどとのやりとりする

$_SESSION['login'] = true;
exit;
```
```index.php
<?php
session_start();
if(!$_SESSION['login']){
    header('Location: login.html');
}
?>
<html>
<body>
    ログイン成功！    
</body>
</html>
```
login.phpにポツンと置かれたログインボタンからGoogleアカウントを選択してログインすれば、いつのまにかinndex.phpに飛んでいてくれるはず。


## お世話になったサイト
- [Integrating Google Sign-In into your web app](https://developers.google.com/identity/sign-in/web/sign-in)
- [Google OAuth 2.0認証を使ったログインの実装](https://qiita.com/kite_999/items/bddd62c395f260e745bc)

### 2020/12/02 更新
この記事を書いたときは勉強不足で知らなかったのですが、[firebase auth](https://firebase.google.com/docs/auth?hl=ja)というとっても便利かつお手軽に認証を実装するという手段もありますのでこちらも参考までに