社内向けの既存のGoogle Apps上に作った各種ウェブアプリケーションを、Androidアプリ化してPCを排除しようという取り組みの中でどうしても避けられない項目の1つが「GoogleのOAuth2.0認証」です。Google Apps ScriptでHTML Serviceを用いてウェブアプリケーションをモバイル向けに作り、CordovaのinAppBrowserプラグインを使って、表示させるようにしても良いのですが、これではアプリを使ってる意味もなく、また複数名で同一の端末を使う事になるので、いかんせん都合が悪い。そこで、inAppBrowserで認証画面を出し、Access Tokenを取得するようにするか?と調べてた所、2017年4月20日にWebView上でのOAuth2.0認証を廃止する(というかブロックする)という記事が・・・

元々、inAppBrowserで認証というのも面倒な事(端末のGoogle Accoutが使えたらいいなぁ)とも思っていたので、調べた所新しいGoogle Sign-Inを使ったCordova向けのプラグインを見つけました。しかし、このプラグインを持ってアプリケーションに実装する方法、実装後のAPIへのアクセス、ログアウトする手順、Access Tokenはどうなるのか?など色々疑問点もあったので、CordovaでAndroidアプリを作成するというシリーズの中では、独立した項目としました。よって、今回は1からプロジェクトを作り直しています。

  • ※Google自身がCordova向けのOAuth2.0認証プラグイン出してくれたらいいのに
  • ※あくまでGoogleのOAuth2.0認証での話なので、他のウェブサービスのOAuth2.0認証ではどうなるのか?は不明。
  • ※WebViewからのOAuth2.0認証は駄目ですが、Chrome Custom Tabsを使った認証はOKみたいです。
  • ※Electronなどのデスクトップ向けのWebViewを利用したものも、駄目みたいですね。とはいえ、ElectronはNode.js側で処理ができるので影響は薄いですが。

目次

必要なもの

  • cordova-plugin-googleplusプラグイン
  • cordovaに設定したパッケージ名
  • クライアント認証キー(フィンガープリント)
  • REVERSED_CLIENT_ID
  • webClientId(いわゆるクライアントID)
  • クライアントシークレット(Google APIへアクセスの時に必要)
  • スクリプトID(Google Apps Script Execution API実行時に必要)
  • 各種スコープ

この他にもビルドを実行するに当たって、Android SDK Managerにおいて、以下のライブラリを入れておく必要があります。ターミナルからandroidを実行して、インストールしておいてください。

  • Android Support Repository(Android Support Libraryも必要ですが、Repositoryが入っていれば使えるようです)。

実装の準備

この作業が最も重要で、最も面倒な作業になるかと思います。一つずつ確かめながら実行して行きます。これから行う作業は、cordovaプラグインとしてcordova-plugin-fcmを含めている場合、このままビルドを実行するとコンフリクトを起こし、ビルドできませんので注意。解決策を講じる必要がありますので、注意が必要です。この下のほうに解決法を記述しておきます。

必ずプロジェクトのバックアップを取ってから作業を行ってください。

パッケージ名を確認する

プロジェクトを作成時にパッケージ名を付けたかと思いますが、これが必要になります。自分でつけておいて忘れてるケースもありますが、忘れてしまった人はconfig.xmlの2行目にあるwidget idがそれですので、確認しておきましょう。

図:赤線の部分がパッケージ名です。

デバッグ用クライアント認証キーを取得する

クライアント認証キーと呼ばれるものを取得します。これには開発向けのデバッグ用とリリース用の2つがあります。詳細はGoogleのリファレンスからも確認できます。このコマンドはターミナルより実行します。リリース時は、パッケージ名が必要になります。

ここでは、androiddebugkeyというエイリアス名で .android/debug.keystoreという場所に保存しています。このコマンド実行時にキーストアのパスワードを求められますが、パスワードはandroidです。すると、SHA1:という項目に値が出ますので、これを取っておきます。この値はフィンガープリントと呼ばれ、デバッグで作成した所、有効期限やいろいろな情報が表示されます。

図:SHA1の認証キーを取得した。

REVERSED_CLIENT_IDを取得する

さて、認証キーを手に入れたので次にこれをもとにして、REVERSED_CLIENT_IDというものを手に入れます。Enable Google Services for your appというサイトを開いて作業を開始します。ちなみにこの作業は、FirebaseでPush通知の下りで取得したgoogle-services.jsonでも手に入れることが可能です。重複して同じアプリ名で登録はできません。

  1. App Nameとしてアプリ名を適当に入れます。今回はCalcAppとしました(Firebaseの方でCalcmanを使ってしまってる為)
  2. Android Package Nameには、取得しておいたパッケージ名を入れます。
  3. Your Country Regionには、もちろん日本を指定します。
  4. choose and configure servicesをクリックして次に進みます。
  5. デフォルトの「Google Sign-In」を選択して、先程取得したSHA1の値を、Android Signing Certificate SHA-1に入力します。
  6. Enable Google Sign-Inをクリックします。
  7. 次にGenerate configuration filesをクリックします。
  8. Download google-services.jsonをクリックしてファイルを入手します。これはFirebaseでのgoogle-services.jsonと同じファイルです。
  9. google-services.jsonをテキストエディタで開いてみます。
  10. oauth-clientのセクションにあるclient_idが今回の目的のREVERSED_CLIENT_IDとなります。Client Typeが1のIDですのでご注意を。
  11. 多分、いつもGASでも利用してるDeveloper Consoleでも作れると思います。

図:ここでSHA1コードを入力します。

GooglePlusプラグインを追加

ここでようやく、cordova-plugin-googleplusをインストールします。これまでのプラグイン同様ターミナルからインストールするのですが、入力コマンドが少し異なるので注意が必要です。ここで、先程取得したREVERSED_CLIENT_IDを使います。

しばらく待つとプラグインがインストールされて、使えるようになります。これで下準備は完了となります。この時のREVERSED_CLIENT_IDは、pluginsフォルダ内のfetch.jsonおよびconfig.xmlに格納されています。リリース時はここのIDを書き換えれば良いと思います。もちろん、書き換える必要がある場合には、別途IDは再取得した上で、Developer ConsoleのSHA1も書き換えが必要です。

webClientIdを取得する

cordova-plugin-GooglePlusを使う上で嵌まるポイントがコレ。Webの情報ではこの後出てくるloginのコードに於いて、webClientIdを入れる場所があるのですが、ここに入れる値はREVERSE_CLIENT_IDの値ではありません。ここに入れるのはそれとは別のClient IDです。ここにREVERSE_CLIENT_IDの値を入れて実行すると、「エラー10」が返ってきて、idTokenも取得できません。

webClientIdですが、google-services.jsonの中に記述されています。oauth_clientのセクションなのですが、client_typeが3のclient_idがソレです。似たような文字列ですが、REVERSE_CLIENT_IDとは値が異なります。わかりにくい場合には、以下の手順でも確認できます。

  1. Developer Consoleに入る
  2. Google APIsのロゴの横のプルダウンをクリックする。
  3. プロジェクトをさらに表示をクリック
  4. Enable Google Services for your appで指定したApp Nameのプロジェクトがあるはずですので、それを選択
  5. OAuth2.0クライアントIDに2つ生成されています。その中のweb clientがwebClientIdになります。Android ClientがREVERSE_CLIENT_IDになります。

この値も大切なものなので、取得しておきます。

図:Developer Consoleから確認したほうがわかりやすい

Firebase Notificationプラグインとコンフリクト

cordova-plugin-fcmとcordova-plugin-googleplusを同じプロジェクトに導入すると、コンフリクトを引き起こしてビルドに失敗してしまいます。かねてより非常に問題だったのですが、cordova-plugin-googleplusのissueにて解決法が提示されました。備忘記録として、以下にその手順を記載しておきます。自分はまだ未検証です。

  1. build.gradleファイルの中から、cordova-plugin-fcm / poc-FCMPlugin.gradleを削除
  2. build.gradleファイルの中へ、com.google.gms.googleservices.GoogleServicesPluginを追加
  3. build.gradleファイルの中のcordova.system.libraryの値を以下のような感じで書き換える

暇があったら、検証してみたいと思います。

Cordova-Plugin-GooglePlusを使う

テストログインをさせてみるために、コードを記述します。Device Readyのあとでないと利用できないので注意。まずは、HTML側から記述します。この辺のコードはgithubでまるごとindex.htmlのサンプルが提供されてるので、そちらを使ってもいいんじゃないかと思います。

DEMOのHTMLでテスト

デモ用として提供されてるindex.htmlで試してみました。プロジェクト内のindex.htmlと置き換えるだけです。特に他に作業は必要ありません。実際に差し替えてログインしてみました。ここまでの作業でおかしなことがなければ、cordova run androidでビルドされアプリが起動するはずです。

サンプルには

  1. GooglePlusプラグインが使えるかどうかのAvailableボタン
  2. Google+へのログインボタン
  3. サイレントログインボタン
  4. ログアウトボタン
  5. 切断ボタン
  6. SHA1署名の取得ボタン

が用意されています。Login with Google+をクリックすると、アカウント選択画面が出て、タッチすると、自分のメアドとプロフィール画像が出てきます。今回imgタグに30pxのサイズを指定しました(デフォルトだとサイズ指定がついていなかったので、デカイ画像が出てしまうので)。

図:アカウント選択画面とログイン後の画像

自分でコードを記述する事例

ログインのみですが、実際にログインして、idTokenをAlertで表示するサンプルです。Scopeに新しくspreadsheetを読み取り専用(https://www.googleapis.com/auth/spreadsheets.readonlyというのがソレ)で加えています。なので1度だけアクセス認証が発生します。

scopesは、利用するGoogle APIのスコープを指定するのですが、省略できます。省略時はprofileとemailがscopeのデフォルトとして利用されます。また、webClientIdには、先程取得したwebClientIdを入力しておきます。実行すると返り値としてJSONが返ってくるので、それがAlert表示されるはずです。

スコープは複数指定可能で、半角スペース区切りで指定します。objはJSON値なので、例えば、obj.emailでメールアドレスを取得可能です。また、webClientIdを指定した場合、obj.idTokenでidToken(JWT)が取得できるようになっています。ちなみに、この画面ではスプレッドシートをSCOPEに入れてるので、その認証画面が出ますが、許可をするとこのページに、接続したアプリとして記録されるようになります。削除をすれば許可を取り消す事が可能です。

 

図:spreadsheetへのアクセス承認とidTokenを表示した様子

ポイント

  • ログアウトボタンやメソッドはきちんと用意しておきましょう。複数名で共有する端末だったり、また一人で使うといっても複数のGoogleアカウントを所有してる場合には、ログアウトしていないと、そちらを選べなくなってしまいます。
  • スコープで指定していないものについては、ログインしていてもそのGoogle APIは利用できません。
  • webClientIdの指定がないとobj.idTokenにてidTokenは取得できません。Nullが返ってきます。
  • webClientIdの指定をした場合、offlineがTrueの時、取得済みのserverAuthのコードが返ってきます。
  • Googleの各種APIはidTokenやそれを用いて取得したAccess Tokenや自動生成されてるAPI KEYが必要になります。どちらもDeveloper Consoleから確認できます。
  • Google APIは、API KEYを利用する方法が一般的ですが、今回のようにOAuth2.0認証での利用が可能になっています。
  • デバッグのフィンガープリントの場合、1時間?くらいで、アプリが使えなくなるみたい。その場合、再度cordova run androidで入れなせばOK.
  • 上記のアプリケーションが使えなくなる原因は、index.html中のapp.initialize();を呼び出すコードが含まれていたのが原因でした。これは不要なので削除したら、動かなくなるという事はなくなりました。

Access Tokenの取得とAPIを叩く方法

Access_Tokenの取得

cordova-plugin-googleplusは、内部的に以下のような処理をしてるようです。

  1. Sing-in成功時にserverAuthCodeとidTokenの2つを取得してる。
  2. 一度アプリを終了しても、次回はサイレントログインできるようになっている。
  3. ログアウトすると、再度アカウント選択画面が出るようになってるが、再認証にはならない。
  4. disconnectした場合は、再度ログイン時にアカウント選択画面と認証画面が出る(追加したSCOPEによる)。
  5. Access Tokenは1時間で期限切れになりますが、再度window.plugins.googleplus.loginを実行すれば、認証なしで続行できます。

このserverAuthCodeとidTokenを持って、各種APIを叩く為のAccess Tokenを取得できるようなのですが、資料が少なくて困りました。まず、前者なのですが、別途「client secret」のコードが必要になります。その為、アプリの中にClient Secretを記述しなければなりません。この方法はセキュアではないので、推奨されないため、配布するアプリなどには使えませんが、以下のコードでAccess Tokenが取得できました。

serverAuthCodeを使った取得法

jQueryを使っていますので、<head>にjsをロードしておく必要があります。また、client secretはweb client側のものを使いますので、Developer Consoleから該当のプロジェクトを開き取得しておきましょう。

実行すると、alertにAccess_Tokenが返ってきました。これで各種Google APIを叩くことができますが・・・Client Secretはコードの中に記述するのではなく、LocalStorageにでも入れておいたほうが良いかもしれません。

idToken(JWT)を使った取得方法

しかし、serverAuthCodeを使った手法は、ウェブアプリケーションならともかく、スマートフォンのアプリとして使うには、Client Secretをアプリに入れなければならないので、セキュアではありません。しかし、cordova-plugin-googleplusでは、直接Access Tokenを返してくれません。返ってくるのは別のidTokenと呼ばれる文字列です。これは、Googleの仕様に従って、プラグイン側で旧GoogleAuthUtil.getTokenメソッドを使わないようにしてる為のようです。

このidTokenなのですが、調べてみると実に深く、公開鍵暗号方式で暗号化された各種データがbase64でエンコードされた文字列して作られてるということのようで。文字列はピリオド区切りで3つながっており、どうやらClient Secretを使わずともこれを使ってAccess Tokenを取得できるようなのです。idTokenの文字列解析に関しては、こちらのサイトでの解説が超詳しいので一度閲覧してみると良いと思います。

さてこのidTokenことJWTと呼ばれる文字列からの取得なのですが、コードは間違っていないと思うのですが、今の所成功していません。コード自体はほとんどserverAuthCodeを使った事例と同じなのですが、自分の場合、401エラーが返ってきます。The OAuth client was not foundというメッセージが返ってきて、うまく行っていません。

POSTの中身がserverAuthCodeの時とはことなり、grant_typeは「urn:ietf:params:oauth:grant-type:jwt-bearer」というものを使用しています。さらにassertionにはidTokenの中身を入れています。Googleのリファレンスでは以下のように定義されています。

項目名 入れる値
grant_type urn:ietf:params:oauth:grant-type:jwt-bearer(要URL Encode。なので、headersでapplication/x-www-form-urlencodedを指定)
assertion idTokenことJWTの値。

400エラーではないのでパラメータが間違ってるわけでないと思うのですが、401が返ってくるので何かミスをしてるのだと思うのですが、ちょっとわかりません。もうちょっとなんだけれどなぁ・・・これでAccess Tokenが取得できれば、Client Secretなしでアプリで完結できるんですが・・・引き続き調査をしてみていますが、いよいよGooglePlus.javaに自らコードを追加していかないと無理かなぁ。

図:サーバには届いてるけれど、401エラー

改造版のGooglePlus.javaを使った事例

【2017年1月29日追記】gylippus氏yhwh氏がフォークしたGooglePlus.javaではobj.accessTokenでダイレクトにaccess_tokenが取得出来るみたいなので、検証してみました。今回は、yhwh氏がフォークしたcordova-plugin-googleplusのGooglePlus.javaのソースで上書きして、実験してみました。結論として、ダイレクトにaccessTokenが取得でき、そのままAPIを叩く事ができました。

  • GoogleAuthUtilを使うので、別途Google Play Services SDKをインストールしておく必要性があります。
  • ※フォークしたJavaのソースを見ると、GoogleAuthUtilにてacct.getEmail()が利用されているのですが、確かこの式はDeprecatedだったと思うので、importの部分でandroid.accounts.Account他を追加した上で、acct.getEmailの部分をnew Account(acct.getEmail(),ACCOUNT_TYPE)といった形にすると良いのかもしれません。
  • ACCOUNT_TYPEは、”com.google“を利用するようです。
  • 詳しくは、こちらのページを参考にするとうまくいくかもしれません。
  • 時間があったら、この改造を施してテストしてみたいです。

以下のソースはyhwh氏のコードで取得したaccessTokenでGoogle Apps Script Execution APIを叩いてみたものです。

Google Apps Script Execution APIを叩くコードの部分は、次項を参照してください。最初の認証で3つのスコープに対して認証を求められ、許可をすると無事にデータを取得できました。これでCordovaにてclient secretなしで直接的にaccessTokenを取得でき、それを元にAPIを叩けるようになりました。素晴らしいです。accessTokenは暗号化プラグインであるcordova-plugin-secure-storageプラグインを使って、localstorageにでも格納しておくと良いのではないでしょうか。

 

図:改造版での認証画面と結果の取得

Google Sheets APIへのアクセス

取得したAccess_tokenを用いて、Google Sheets API v4を叩き、データの取得をしてみたいと思います。とりあえず、idTokenからのAccess Tokenは今もまだ模索中で、GooglePlus.javaにGoogleAuthUtil.getTokenで結局取得できないか挑戦中であるため、ここではserverAuthCodeにてClient Secretを使って取得したAccess Tokenを元にコードを記述しています。

Access Tokenが手に入ると、非公開のスプレッドシートであっても自分のアカウントでアクセスしてるわけなので、アクセスする事が可能です。今回の方法はGoogle JavaScript Client Libraryを使っていますが、GET/POST通信でもいけると思います。

ソースコードと実行結果

試しに非公開としてる自分のスプレッドシートにアクセスしてみました。すると以下のような画面がAlertで表示されました。

図:スプレッドシートの値が返ってきた

ポイント

  • gapi.auth.setToken()にて、他で取得済みのAccess Tokenをセットする事が可能なので、改めて認証作業は不要です。
  • Sheets API v4のエンドポイントURLは、https://sheets.googleapis.com/$discovery/rest?version=v4です。
  • Client Library for JavaScriptはSheets API以外も叩けるので、随時色々なAPIを実行してみましょう。ただし、取得済みAccess TokenのScopeの範囲内となるので、範囲外のAPIを叩いてもエラーになります。
  • 今回は読み取りだけなので、scopeはhttps://www.googleapis.com/auth/spreadsheetsを入れていますが、名前の変更やファイル作成の場合には、https://www.googleapis.com/auth/driveもスコープに入れる必要性があります。詳しくはこのページを参照。
  • Sheets API v4よりデータ取得以外でも、データの書き込み、スプレッドシート自体の操作、書式設定、グラフ作成、フィルタなど様々な事がAPIより可能になりました。とはいえ、基本読み書きが出来ればプログラム的には十分ですが。
  • 無事アクセスできるとJSONで取得できます。JSON.stringifyで配列に変換しています。これを色々加工して、Cordova内に表示してあげれば完成です。今回はAlert表示で手を抜いています。
  • https://apis.google.com/js/api.jsのほうのクライアントライブラリではsetToken()がないので使えません。なんで2種類あるのだろう。
  • ちなみに以下のような感じでAccess Tokenをつなげて、アクセスも可能のようです。もちろん()は記述する必要はありません。下記のaccess_tokenはAPI KEYでも行けるようです。

Google Apps Script Execution APIを叩いてみた

1回やってみて、失敗したので、Execution APIは叩けないと思っていたら、出来ました。但し、これを実現する為にはいくつかの手順が必要になります。今回はスプレッドシートのデータを取得した上で、適当なメールをGAS側で送信するというものです。Execution API自体の詳細な内容はこちらのページを参照してください。ここでは嵌まるポイントや注意点を中心に記述します。

事前準備

  1. 今回使用するスプレッドシートにデータと関数類を入れておく。ちなみに非公開のファイルです。
  2. Developer Consoleを開き、Cordovaで用意しておいたプロジェクトを開く
  3. ライブラリより「Google Apps Script Execution API」を有効にする
  4. プロジェクトを設定を開く
  5. プロジェクト番号が表示されるので控えておく
  6. スプレッドシートのスクリプトエディタに於いて、Developer Consoleプロジェクトをクリック
  7. プロジェクトを変更にて、5.で手に入れたプロジェクト番号を入れて紐付けする
  8. スプレッドシートのスクリプトエディタにおいて、プロジェクトのプロパティ -> スコープを開く
  9. 今回は4つスコープが出てきたので控えておく。このスコープはindex.html内のSCOPESに入れて、認証時に使います。
  10. 一回、GAS側でonOpenでも実行しておいて、スクリプトの認証作業を完了しておいてください。この時、自分のアイコン以外にDeveloper Consoleでアイコンを設定しておくとその画像も出てきたりします(自分はキノコのアイコンを登録しています)。

図:スクリプトIDが重要です。

図:プロジェクト番号でGASと紐付け

図:GAS承認時にキノコのアイコンも出た

とりあえず、これでGAS側とDeveloper Consoleの設定は完了です。続いて、Cordova側ですが、控えておいたスコープをコードの中に記述してあげます。それらを含めてソースコードにGoogle Apps Script Execution APIを叩くコードを記述します。

ソースコードと実行結果

Cordova側のソースコード

GAS側のソースコード

これで実行をしてみたら、非公開のスプレッドシートのデータをGoogle Apps Scriptが拾ってきて、返してきてくれます。また、適当なメールをログインユーザ宛に送るようにしています。ただしAPIの実行権限は、今回は自分のみにしました。

図:いちごデータベースの値が取得できた

ポイント

  • ファイルが非公開でもAPIはAccess Tokenがあれば叩くことが可能です。
  • Script IDとAPI IDを間違えやすいので注意。使うのはプロジェクトプロパティに表示されてるスクリプトIDです。
  • ただし、GAS側のAPI実行権限が自分だけの場合、第三者はAccess Tokenがあっても叩けません。404エラー(Request entity was not found)が返ってきます。
  • Sheets APIと違ってデータの操作関係をすべてGAS側に任せる事ができるので、クライアント側で妙なロジックを組む必要がありません。これは素晴らしい
  • GAS側で色々APIを使うとSCOPEも変わってくるので注意。機能増強時に追加されたスコープが、Cordova側でも追加されていないとエラーになりかねません。
  • GASができる事はすべてこれで可能になるので、ボタン一発で様々な処理をCordovaで発火させることが可能になるので、超おすすめ。
  • 今、これで構築中なのは、第三者からGASで作ったワークフローシステムにおいて、Cordovaで承認・却下を実行すると、それが書き込まれ、第三者にメールと共に自動生成されたPDFファイルや承認結果の内容を通知するといったものですが、Cordova側はUIに徹して、大規模な処理をGAS側であれこれやってもらうものを考えています。

関連リンク

cordovaでOAuth認証系

Cordova Plugin開発系

idTokenとJWTについて

Google API系

読み物系

古い情報

Pocket
このエントリーをはてなブックマークに追加
Bookmark this on Yahoo Bookmark
Pocket