wpionic.tokyo #3 OAuthによるWordPressログイン

ionic+WordPressによるモバイルアプリ作成勉強会 wpionic.tokyo の第三回リポート。前回はionicによる画面の作成を行ったが今回は投稿や削除という、もう少し先の段階まで進んだ。

OAuthプラグインを導入する

モバイルアプリとWebサイトを連携させる場合、多くはOAuthと呼ばれる認証形式を採用する。WordPressの場合は OAuth 1.0a Server というREST APIコアチームの面々が作成したものがあるので、それを採用する。

このアプリを利用すると、クライアントアプリケーション(認証を利用する側のアプリ=この場合はモバイルアプリ)を登録することができ、それに対するクライアントキーとクライアントシークレット(※コンシューマーキーとか、コンシューマーシークレットと呼ばれることもある)が発行される。

現在は管理画面からコンシューマーキーを発行できるようになった。

ポイントはコールバックに oob と入力すること。OAuthはリダイレクトを行うので、セキュリティを担保するためにそのコールバック先を指定するのが通常だ。しかし、モバイルアプリは URL を持たないので、リダイレクトする先がない。そこで、PINコードを発行することで、その代替とするのである。

余談だが、こうしたOAuth1.0aの仕組みは実のところ、少し時代遅れだ。昔のtwitterクライアントは、このPINコードを表示する画面が一瞬表示され、すぐに閉じるという仕様だったのだが、それはこのOAuth1.0aの仕様による。

さて、これでサーバー側での対応は取り急ぎ完了だ。

OAuth1.0aに対応したクライアントを導入する

ionicは基本的に Angular2 ベースなので、Angularに対応したOAuth 1.0aクライアントが存在していれば OK である。簡単に見つかるかと思ったが、 StackOverflowで見つけたクライアント ddo/oauth-1.0a はうまく動かすことができなかった。これは筆者がまだES6やtypescriptに不慣れだったことが原因である。

したがって、やや古いライブラリではあるが、利用実績のある bytespider/jsOAuth を採用することになった。

設定値を保存する

さて、ソースコードをGithubのパブリックリポジトリで公開しているということもあるのだが、WordPressから発行された認証情報(クライアントキー・シークレット)をどこに保存したらよいのだろうか?

また、ステージング環境やプロダクション環境など、複数の異なる環境でのテストを行う場合、環境変数などで読み込むJSONファイルを切り替えるようなことがよく行われる。できれば、そららのJSONは .gitignore などでコード管理の対象外としたいものだ。実際、筆者はnodeプロジェクトでそうしている

筆者は ionic にもそうした機能が当然あるものと思っていたのだが、存在しないらしいことがわかった。いくつかググった結果、wwwフォルダ以下に存在するJSONを読み取るライブラリ gl-ionic2-env-configuration を作った人がいたので、それを利用した。バージョンが 0.0.8 と低すぎるのだが、ないものはしょうがない。

ionicで開発する場合にハマるポイント

高橋 文樹 による追記 @ 2017年7月8日

すっかり忘れていたのだが、ionic+WordPressで開発する場合、問題となる点が2つあるので、それを解消する方法を追記しておく。

その1. CORS問題

ionicで開発する場合、多くはローカルサーバを立ち上げ、そこにブラウザでアクセスする形をとる。この場合、問題となるのが CORS である。

ionic serve コマンドで立ち上がる http://localhost:8000 がデフォルトのローカルサーバーになるのだが、たとえばここから https://capitalp.jp にXMLHttpRequestを飛ばす場合、ブラウザがエラーを返す。これはブラウザのセキュリティ機能である。

ionicはJavascriptであり、上で採用したjsOAuthはXMLHttpRequestを利用しているので、このエラーにひっかかる。ちなみに、アプリとしてビルドした場合にはこの問題は発生しないため(ブラウザの挙動なので)、公式ブログでもわりとめんどくさそうに回答している。

解決策としては、サーバーとなるWordPress(上の場合なら capitalp.jp)にクロスドメインポリシーをヘッダーとして送信させるというものがあるのだが、これはオススメしない。常にサーバーのヘッダー送信設定(.htaccessやnginx.conf)をいじれるとは限らないからである。

余談だが、 enable-cors.org というサイトが存在することに、どれほど多くの人がこの問題に苦しんでいるのかを感じさせる。

閑話休題。一番簡単なのは、ブラウザの設定をいじってCORSチェックをオフにすることである。Safariだとメニューの「開発 > クロスオリジンの制限を無効にする」にチェックを入れるだけだ。

このメニューは筆者がいままで発見したSafariのメニューでもっとも嬉しかったものの一つ。

Chromeの場合はCORSチェックをオフにする拡張があるようだが、ちゃんと動いているのかわからなかったので、うまくいった方は報告してほしい。

その2. WordPressのCookie認証が動いてしまう

CORS制限を突破して無事にリクエストを送ったものの、WordPressがXMLHttpRequestについてるCookieを読み取ってなんか変な感じになってしまうという問題が発生する。

詳細は省くが、要するに……

  • OAuth 1.0a Serverプラグインは、WordPressのREST APIにおけるユーザーに認証フローに後付けでユーザー認証を追加する。
  • なぜなら、RESTへのリクエストでCookieとOAuth Tokenが両方送られてくることはないからである。ところがどっこい、ionicのローカル開発ではそれが起きてしまう。
  • XMLHttpRequestでのアクセスでも、どうやら同一ドメインのCookieは読み取れるようで、しかも自分のWordPressなので、ログイン済みだったりする。そうすると、「あ、高橋さんですよね? ちーっす」という具合にCookieでのログインが成功してしまう。
  • ところが、REST APIのクッキー認証では _wpnonce というキーでnonceを送らないといけないことになっているので、「はいCSRFおつ」となってすべてのリクエストが401になってしまう。

これを防ぐ方法がないかを探したのだが、結局のところ、「リファラーにlocalhostが含まれていたらCookie認証をそもそもしない」というややダサい感じのハックをせざるを得なかった。

// Kill send cookie if origin is localhost
add_action('after_setup_theme', function(){
       error_log( 'Check if this is a rest' );
        $is_ionic_local = isset($_SERVER['HTTP_REFERER']) && false !== strpos( $_SERVER['HTTP_REFERER'], 'localhost' );
        if ( $is_ionic_local ) {
                error_log( 'This is ionic request!' );
                remove_action('determine_current_user', 'wp_validate_auth_cookie' );
                remove_action('determine_current_user', 'wp_validate_logged_in_cookie', 20 );
        }
});

これに関しては、今後もう少しスマートな解決方法を探りたい。ライブラリのリクエスト時にCookieを送らないようにすれば良いのだろうか? 識者の意見が待たれる。

接続を行う

実際に接続を行うコードは次のようになる。

// 設定の取得
this.config = envConfiguration.getConfig();
// OAuthクライアントを作成
this.oauth = new JsOAuth.OAuth({
  consumerKey: this.config.clientKey,
  consumerSecret: this.config.clientSecret,
  requestTokenUrl: this.config.requestTokenUrl,
  authorizationUrl: this.config.authorizationUrl,
  accessTokenUrl: this.config.accessTokenUrl,
  callbackUrl: 'oob'
});

// その後認証処理を挟んで……投稿を行う
this.oauth.post(
   "https://wpionic.tokyo/wp-json/wp/v2/posts",
   {
    'title': 'REST API',
    'author': 1,
    'content': 'はじめてのコンテンツ'
   },
   ( data ) => {
      console.log(JSON.parse(data.text));
   },
   ( data ) => {
      console.log(data);
   }
);

これで無事投稿を行うことができた。具体的なコードはリポジトリのソースをみていただきたい。

問題点

実際に作って見たところで、幾つか留保すべき点も発見したので書いておこう。

  • 今回の認証ではOAuth1.0aを採用したので、一度Webサイトに移動する形式になっているが、モバイルアプリとしてこの遷移を必須とするのはやや「ブサイク」である。
  • おそらくだが、多種多様なクライアントからのアクセスを受け入れるというより、多くの需要は「自分のカスタマイズされたWordPress専用のモバイルアプリを作りたい」というものだと思うので、OAuth1.0aが最適かどうかは微妙だ。
  • WordPressコミッターやコアチームメンバーを多く抱えるHuman MadeのJoe Hoyleは Authentication Broker という仕組みがあることを教えてくれたのだが、詳細を見ていないので、よくわからない。 このAPIを利用すると、モバイルアプリとしてはかなり自然なログインフローになる。
  • 認証後の情報を引き回す機能(いちどログインしたらそのまま)が当然必要になるのだが、アプリごとにその要求仕様が異なる。あるアプリはログインしていないとまったくなにもできない(ex. twitterアプリ)ようにしたいだろうし、別のアプリは「あることをするときだけ認証を要求する」ようにしたいだろう。こうした需要に応えるためのライブラリを作成するのはなかなか骨が折れそうだ。
  • 採用したjsOAuthライブラリにはPOSTとGETしかメソッドがなく、DELETEやPUTができなかった。なんとかしないと……

今後の予定

目的がないとつまらないので、なにか具体的な目標を掲げることになった。ionicからsiriなどの音声認識機能を利用できるらしいので、音声入力でブログがかけるアプリを作成しようということになった。

参加者

筆者が WordCamp Europe から帰国してすぐのことだったので、事前告知が不足したのか、筆者の人望がないのか、参加者が激減し、4名となった。少し寂しいので、7/26に行われる wpionic.tokyo #4 では、ぜひみなさんの参加をお待ちしている。

保存保存

保存保存

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください