ReactとGoogle Sign-In

Google Sign-In をReactなSPAで使った時の日記です。

サーバーとの連携やセッションの管理について少し悩んだのでメモっておきます。ちょっと長くなりそうなので何回かに分けます。

Google Sign-In

Google Sign-Inと言っているのはこれのこと。 developers.google.com

こちらにしたがって、Google Cloud Platformの認証情報を作成し、クライアントIDを取得する。「承認済みの JavaScript 生成元」として localhost:3000 を指定しておく。

取得できたら早速使ってみる。まずはReactなしで試す。必要なのは以下の4つ。

  • metaタグに取得したクライアントIDを指定する
  • https://apis.google.com/js/platform.js を読み込むscriptタグを用意する
  • g-signin2 というclass属性のついたdivタグを用意する
  • 上のdivタグに data-onsuccess 属性でログイン成功時のコールバックを指定する
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>sample</title>
  <meta name="google-signin-client_id" content="<CLIENT_ID>">
  <script src="https://apis.google.com/js/platform.js" async defer></script>
</head>
<body>
ようこそ!
<div class="g-signin2" data-onsuccess="onSignIn"></div>
<script>
  function onSignIn(googleUser) {
    var profile = googleUser.getBasicProfile();
    console.log('ID: ' + profile.getId());
    console.log('Name: ' + profile.getName());
    console.log('Image URL: ' + profile.getImageUrl());
    console.log('Email: ' + profile.getEmail());
  }
</script>
</body>
</html>

<CLIENT_ID>Google Cloud Platformのコンソールで取得した値にすること。適当なWebサーバーを3000ポートで起動しブラウザで localhost:3000 にアクセスする。

https://i.gyazo.com/554cfc0cfca3bb9bb1d49c76f92648f0.png

ログインボタンをクリックしてGoogleアカウントでログインする。ブラウザのコンソール出力を見ると以下のような出力が。

ID: 111
Name: hoge
Image URL: https://lh4.googleusercontent.com/fuga/photo.jpg
Email: foo.bar@gmail.com

おぉー、簡単!

Reactと一緒に使う

ReactアプリケーションでGoogle Sign-Inを使ってみる。

create-react-appでアプリケーションの雛形を作成する。使った create-react-app のバージョンは

$ create-react-app --version
1.5.2

package.jsonのdependencies。

  "dependencies": {
    "react": "^16.3.0",                      
    "react-dom": "^16.3.0",          
    "react-scripts": "1.1.1"                            
  },

雑にさっきのコードをsrc/App.jsに移植する。jsxでは class 属性は className と書くのでそこは直しておく。

class App extends Component {
  onSignIn = (googleUser) => {
    var profile = googleUser.getBasicProfile();
    console.log('ID: ' + profile.getId());
    console.log('Name: ' + profile.getName());
    console.log('Image URL: ' + profile.getImageUrl());
    console.log('Email: ' + profile.getEmail());
  }
  render() {
    return (
      <div>
        ようこそ!
        <div className="g-signin2" data-onsuccess="onSignIn"></div>
      </div>
    )
  }
}

$ npm start して、 localhost:3000 にアクセス。

https://i.gyazo.com/97aa819625a7923ef60634e2fcd448eb.png

む、ログインボタンが表示されない。metaタグとscriptタグを追加していなかった。public/index.html にmetaタグとscriptタグを追加する。

--- a/public/index.html
+++ b/public/index.html
@@ -19,6 +19,8 @@
       work correctly both with client-side routing and a non-root public URL.
       Learn how to configure a non-root public URL by running `npm run build`.
     -->
+    <meta name="google-signin-client_id" content="<CLIENT_ID>">
+    <script src="https://apis.google.com/js/platform.js" async defer></script>
     <title>React App</title>
   </head>
   <body>

https://i.gyazo.com/554cfc0cfca3bb9bb1d49c76f92648f0.png

表示された。ログインしてブラウザのコンソール出力を見ると...

何も表示されない。 data-onsuccess="onSignIn" で指定したコールバックが呼ばれないですね。まぁそりゃそうだなぁ。

react-google-login

どうしよっかなぁと思ってGitHubを眺めてるとreact-google-loginというのを発見。

github.com

試してみたけど、ログインボタンをrenderするときにクライアントIDを指定する方式になっていて、ログイン済みの状態でアクセスした時にログアウトボタンだけ欲しい場合に都合が悪かったので使わないことにする。

JavaScriptコードからログインボタンを作成する

Googleのドキュメントを読んでみると、自分のJavaScriptコードでログインボタンを作成できることが分かる。この時にコールバックを指定できる。この方法を使えば以下のような感じでログインボタンを描けそう。

gapi.signin2.render('google-signin-button', {
  'onsuccess': this.onSignIn,
  'onfailure': (err) => console.error(err)
});

さらにリファレンスを見ると、metaタグでclient_idを指定してたけど、これもJavaScriptで指定できるようだ。

gapi.load('auth2', () => {
  gapi.auth2.init({client_id: <CLIENT_ID>})
    .then(
      (result) => console.log(result),
      (err) => console.error(err)
    );
})

これらを使って、 initSignInButton メソッドを追加する。

--- a/src/App.js
+++ b/src/App.js
@@ -1,6 +1,20 @@
 import React, { Component } from 'react';
 
 class App extends Component {
+  initSignInButton = (gapi) => {
+    gapi.load('auth2', () => {
+      gapi.auth2.init({client_id: <CLIENT_ID>})
+        .then(
+          (result) => {
+            gapi.signin2.render('google-signin-button', {
+              'onsuccess': this.onSignIn,
+              'onfailure': (err) => console.error(err)
+            });
+          },
+          (err) => console.error(err)
+        );
+    })
+  }
   onSignIn = (googleUser) => {
     var profile = googleUser.getBasicProfile();
     console.log('ID: ' + profile.getId());
@@ -26,7 +42,7 @@ class App extends Component {
     return (
       <div>
         ようこそ!
-        <div className="g-signin2" data-onsuccess="onSignIn"></div>
+        <div id='google-signin-button'></div>
       </div>
     )
   }

あとは gapi オブジェクトをどうやって取得するか。Googleのドキュメントのサンプルにある <script src="https://apis.google.com/js/platform.js?onload=renderButton" async defer></script> というところ。scriptタグでGoogleのjsファイルを読み込んでるところをどう書き直そう(npmになさそう・・・)。先ほどの react-google-login のコードを調べてみると、DOMをごにょごにょしてscriptタグを動的に書き込んでた。それを真似する。

--- a/src/App.js
+++ b/src/App.js
@@ -1,6 +1,19 @@
 import React, { Component } from 'react';
 
 class App extends Component {
+  componentDidMount() {
+    this.downloadGoogleScript(this.initSignInButton)
+  }
+  downloadGoogleScript = (callback) => {
+    const element = document.getElementsByTagName('script')[0];
+    const js = document.createElement('script');
+    js.id = 'google-platform';
+    js.src = '//apis.google.com/js/platform.js';
+    js.async = true;
+    js.defer = true;
+    element.parentNode.insertBefore(js, element);
+    js.onload = () => callback(window.gapi);
+  }
   initSignInButton = (gapi) => {
     gapi.load('auth2', () => {
       gapi.auth2.init({client_id: <CLIENT_ID>})

public/index.html に追記したタグは不要になったので消す。

--- a/public/index.html
+++ b/public/index.html
@@ -19,8 +19,6 @@
       work correctly both with client-side routing and a non-root public URL.
       Learn how to configure a non-root public URL by running `npm run build`.
     -->
-    <meta name="google-signin-client_id" content="<CLIENT_ID>"
-    <script src="https://apis.google.com/js/platform.js" async defer></script>
     <title>React App</title>
   </head>
   <body>

localhost:3000 にアクセスして、ログインしてみると...うまくいった!

おしまい

次回は、react-routerでログイン前と後で画面分けたりする、予定。

最終的に create-react-app した後に編集したのは src/App.js だけ。