「PHPをやっていきましょう」みたいな本を書きました

ご縁があり、同じ会社のサーバーサイドやインフラをやっている人達で本を書かせていただくことになりました。 「はじめてのPHPプロフェッショナル開発 」というタイトルです。

f:id:o0h:20190223170503p:plain
Amazonで「カテゴリ1位」になってて嬉しい(ドキドキ・・

どんな本ですか?

少し、書き手から見ての内容について触れます。

プロフェッショナル本の立ち位置

「どんな本であるか」という事については、一緒に取り組んだ & 今回の取り組みにあたって諸々のプロジェクトマネジメント的な役割も務めてくれた id:itosho525 の記事がわかりやすいです。

itosho525.hatenablog.com

もちろん、PHPの本ではあるのですが、扱っているトピックが多いので、ややもすると「帯に短し襷に長し」という印象を持たれるかもしれません。ただ、そういう印象はある意味間違っておらず、プロフェッショナル本は 入門書は巷に(特にPHPは)溢れているものの、それを読み終えた初心者が実際の現場で活躍するための次に読むべき本がないのでは? という問題意識から企画が始まっています。

ですので、プロフェッショナル本は最新のPHPを学びながら入門書と個別具体的な技術の専門書との架け橋となる本を目指して書きました。

f:id:o0h:20190223211922p:plain 『はじめてのPHPプロフェッショナル開発』という本が出版されます - comix

「一通りのトピックを扱ってみよう」というのも意識した内容であり、引用したブログ記事の図中にある通り「より専門的な内容は、それぞれの文献を探してそっちで掘り下げてみる」というような使い方になってくれれば良いなと思っています。

設計やコーディングについては、詳細で高度なとても優れた書籍が非常に多く存在しています。
単体テストも、技法・設計については書籍などがありますし、テスティングフレームワーク個別の話は、コアコードやそれを利用しているプラクティスに振れることで多くのことを学べるのかと思います。
CakePHPについては、書籍としては「上級者向けの決定版」と言えるようなものは未だ無いかもしれませんが、GitHub/Slackなどのコミュニティ、コアチームの面々を始めとした強力なデベロッパの情報発信を追うのが良いでしょう。
コンテナ技術においては、まだまだ非常に変化の激しい分野でもあると思うので、最新の情報だったりGCPやAWSといったPaaS/IaaSプロバイダの発する情報・コミュニティの発する情報を拾ったり、専門書籍から「良いやり方」を見つけていけるようにも思います。

どの領域も「この本の次」があることを意識しながら執筆チーム一同取り組んでいました。
「どこから手を付ければよいかわからない」「そもそも全体像がわからない」時に、道を指し示せればと思います。

現場感

そのように制作を進めていく中で、大事にしていたことの1つが「現場感」です。

「一通り〜」というのを意識していた、と上に書きました。そのため「体系的」「網羅的・全体的」という性格をもたせたいとは願っています。
とはいえ、「あまりにも『座学的』『教習的』でありすぎては、この本で想定している読者がワクワクしないかもね」というのは、企画段階から出ていた話です。

今回の執筆チームについては、普段から「自分がバリバリ現場でやっている」面子です。また、そもそも普段の活動が「上流や下流の別け隔てがない(そのチーム規模にない)組織で、自社サービスを提供する」というものです。
それを踏まえて、「自分たちがやっている・やりそうな内容を、エッセンスとして抜き出して並べれば、ある程度の『現場の動き』が現れてくるのではないか」という発想で、書籍内容のアウトラインが組まれました。

おそらく「この一冊で、PHPのプログラムをとことん書けるようになるぞ!!」というには、実際のPHPコードが登場する章は少なく感じられるでしょう。
裏を返せば、具体的なコードの比率をある程度抑えてまで「触れておきたかった」話を扱っています。例えばチーム開発の話題だったり、CIについて丸々1章を割いているのは、その顕れと言えます。

CakePHP

「『入門』という単語を冠したPHP本にしては、相対的に見てコードが少ない」と述べました。とはいえ、「丸々1つのWebアプリケーションを作成する(テストも込みで)」ところまでは扱います。

アプリケーションの作成にはCakePHPを選択しました。
実際に出てくるコードは、「CakePHPをめちゃくちゃカッコよく使っている」と言うには不足があるかもしれません。確かに、もっと「尖った」「Cakeぽく」書ける部分はあるなと感じます。
CakePHPの本ですか?といえば、そこが主眼でもないし、また「動くようにアプリケーションを作成する」のを本質として説明を絞ってもいるからです。

それでも、Cakeアプリケーションがテストコードも揃った状態のアプリケーションが、解説付きで世の中に出されるという事は、いちCakeファンとして嬉しいなぁと思います。

この本は、位置づけとして「『プログラミングをする』と『チームでサービスを作る』の間の架け橋」ものです。そういう意味では「PHP」の部分を置き換えても良かったかもしれないし、まして「CakePHP」の部分は他フレームワークに置換しても、同様に成り立つような感覚があります。
インターフェイスと具体実装の関係のような話ですが、少なくとも「現場っぽいやつ」としては具体的な話が必要なわけで、その目的でCakePHPを舞台に立たせました。
結果として、公式のチュートリアルよりはやや手厚めで、GitHub等に置かれている一般的なOSSよりも日本語情報が揃っていて、各実装部分・テスト部分における「代表的なスニペット」よりも網羅的に「ちゃんと運用できそうなくらい」のコードが揃っている・・というサンプルコードが作成されています。

担当パートについて

アウトラインは、以下のようになっています

  1. Part01 導入編
    • Chapter01 進化するPHP
    • Chapter02 PHPのエコシステム
    • Chapter03 PHPをはじめよう
    • Chapter04 モダンPHPの文法と基礎文法
  2. Part02 入門編  * Chapter05 チームのための開発環境構築
    • Chapter06 設計から始める
    • Chapter07 CakePHPを使ってみよう
    • Chapter08 質問と回答機能の実装
    • Chapter09 ユーザー管理機能の実装
    • Chapter10 テストコードを書く
  3. Part03 実践編
    • Chapter11 チーム開発の現場
    • Chapter12 Pull Request 駆動によるコードレビュー
    • Chapter13 開発に役立つツール
    • Chapter14 継続的インテグレーション
    • Chapter15 デプロイの自動化
  4. Part04 発展編
    • Chapter16 障害と向き合う
    • Chapter17 SQLチューニング
    • Chapter18 PHPとセキュリティ
    • Chapter19 外の世界に飛び出そう

このうち、私は

  1. Chapter01 進化するPHP
  2. Chapter02 PHPのエコシステム
  3. Chapter13 開発に役立つツール
  4. Chapter14 継続的インテグレーション
  5. Chapter16 障害と向き合う

及び「Chapter04 モダンPHPの文法と基礎文法」の一部(PHP7の新文法の話題)と、自分の担当Chapterのコラム、その他の一部コラムを担当しました。

言い換えると、今回は書籍内におけるアプリケーション作成部分は他のメンバーが書いてくれており、私は眺めていた形に😇 *1

そんな主観もあってか、「これからより『チーム』や『プロジェクト』でのサービス開発に踏み込んでいくにあたっての、橋渡しになる一冊」になれば良いなぁと感じているところです。

あとがき

ツイッターでも言及してくださっている方がいたりと、大変ありがたく感じています。
発売してからの反応がどうなるか、が最も肝心なのですが・・・・・心臓が飛び出そうですね!
誰かの役にたてばいいな〜と思います。

アフィリンクを貼りますので、よろしければポチっとお願いします😉

TECHNICAL MASTER はじめてのPHPプロフェッショナル開発 PHP7対応

TECHNICAL MASTER はじめてのPHPプロフェッショナル開発 PHP7対応

*1:とはいえ、全くPHPを書いていない!!!ということではなく、サンプルコード公開用のレポジトリにある、テストコードの作成はヘルプしたりしています

Firebase Authenticateに触れてみる②

前回の続き!!

daisuki.nichiyoubi.land

取り急ぎの作業としては

  1. cakephp-jwt-auth の仕事や中身について理解する
  2. 「How to add JWT Authentication to a CakePHP 3 REST API」で説明されているシナリオについて理解する
  3. 実際にFirebase Authenticateを利用したGItHubログインを実装してみる

desu!

・・・の前に、一旦おさらい

qiita.com ありがとうございます、ありがとうございます・・・

こうしたモチベーションというか背景みたいなのを理解した上で、「どの部分を使うのか」っていうのも理解しておかないといけない。
リンクされている記事に、「各フィールドはそれぞれ何なの」という解説があった。

JWTについて簡単にまとめてみた - hiyosi's blog

「署名付きでエンコードされて渡ってくるJSONだよ」みたいなもんなのだけれども、この「検証」「デコード」ができないといけないよね、的な。 で、そのあたりのビジネスロジックを「JWT認証プラグイン」に期待するわけで、手っ取り早くソースコード読んで見る。

cakephp-jwt-authの中身をかいつまんで見る

実態は JwtAuthenticate.php に入っている。*1

cakephp-jwt-auth/JwtAuthenticate.php at master · ADmad/cakephp-jwt-auth · GitHub

多分、関心を持たないと行けないのは

  1. getToken()
  2. _decode()

の2つだな。次点で getUser() があって、 sub フィールドを用いたfind()を打っているのが目に入るけども、これはAuthComponent/AuthenticateというCakePHP一般の話になるので割愛。 subは認証側のユーザー識別子。

    /**
     * Get token from header or query string.
     *
     * @param \Cake\Http\ServerRequest|null $request Request object.
     *
     * @return string|null Token string if found else null.
     */
    public function getToken($request = null)
    /**
     * Decode JWT token.
     *
     * @param string $token JWT token to decode.
     *
     * @return object|null The JWT's payload as a PHP object, null on failure.
     */
    protected function _decode($token)

となると、このクラス自体は「検証」をしていなくて。
鍵を使うとかの話はどこに・・・?っていうのを気にしていくと、 _decodeの中にある。

<?php
         $payload = JWT::decode(
                $token,
                $config['key'] ?: Security::getSalt(),
                $config['allowedAlgs']
            );

となっているので、configに「検証のための情報を渡さなきゃいけないのね!!」っていうのが見て取れて納得感を得られるわけです。

「How to add JWT Authentication to a CakePHP 3 REST API」で説明されているシナリオについて

いったん、こちらの記事に立ち返ります。

www.bravo-kernel.com

行っているのは

  1. ユーザー登録用のエンドポイント /api/user/register -> IDトークンを返す
  2. ログイン用のエンドポイント /api/users/token -> IDトークンを返す
  3. その他の通常のエンドポイント利用時に「トークンの検証を行う」

という感じでしょうか。

我々と前提が違うのは、

  • JWT認証サーバーも兼ねている
    • 我々は、認証についてはFirebaseに任せっきり!
  • 認証を任せていることで、2つの要件が増える
    • ユーザー登録 & tokenを生成・発行する能力が必要
    • 非ログイン→ログイン状態を生み出すための、「ログイン処理」も自前でもつ必要がある

「トークンを生成する」というのは、この部分ですね。
https://github.com/bravo-kernel/application-examples/blob/master/blog-how-to-add-jwt-authentication-to-a-cakephp-3-rest-api/src/Controller/Api/UsersController.php#L27

「salt」を自前で設定したり、 「sub]をセットしたり〜というのは、今回の我々においてはノータッチな部分です。
逆に、認証サーバー側と共通の認識を持った「鍵」を用意する必要があるわけですが。

「ログインをする」というのは、AuthComponentで「Form Authenticate」を設定している部分となります。
email/passのPOSTをしてログイン→IDトークンください!というのがコレ。

f:id:o0h:20190105214233p:plain

あるいは、コード中で言えば次の部分です

https://github.com/bravo-kernel/application-examples/blob/master/blog-how-to-add-jwt-authentication-to-a-cakephp-3-rest-api/src/Controller/Api/UsersController.php#L46-L49

という感じで、全容とそれに対するサンプルアプリケーション実装の対応がつかめてきたかなーという感じです。

実装してみよ!

ということで、我々の場合で言うと

(まずはユーザー登録側)

  1. Firebase Authenticateによるログイン後に
  2. クライアント側で取得したIDトークンを、api/users/signup に投げて
    1. これはHeaderで渡す
  3. Usersレコードを作成後、ログイン状態の確立

まで、まずは行ければOKでしょうか。
(本来はajaxなアプリケーションにして、毎回headerに入れてもらったほうが良い気がするけども。まぁ練習用なので・・・)

認証用に、usersテーブルに2列追加

もはや我々は「どうすればJWTで渡ってきた情報をこちらの情報と同定できるのか」を把握していますので、それを達成するためにDBスキーマを揃えます

usersテーブルに、 sub は必須そうなので加えておくのと、 「どのサービスからのログインか」を見るために provider も足しておきます。こちらは github.com などの情報が入る想定。(直接つなぐなら、 iss なんだろうなー)((Firebase Authenticate的には uid の方が好ましいのかな?))

bin/cake migrations create AddJwtAuthColumnsToUsers
<?php
use Migrations\AbstractMigration;

class AddJwtAuthColumnsToUsers extends AbstractMigration
{
    /**
     * Change Method.
     *
     * More information on this method is available here:
     * http://docs.phinx.org/en/latest/migrations.html#the-change-method
     * @return void
     */
    public function change()
    {
        $this->table('users')
            ->addColumn('sub', 'string', [
                'default' => null,
                'limit' => 128,
                'null' => false,
                'after' => 'avatar_url'
            ])
            ->addColumn('provider', 'string', [
                'default' => null,
                'limit' => 32,
                'null' => false,
                'after' => 'sub'
            ])
            ->update();
    }
}

どんなものを実装すればいいのか(認証周り・サーバー)

やりたいことは

  • ヘッダー中にIDトークンを入れて投げれば良い
    • デフォルトだと authorization フィールドになっている
  • IDトークンのデコードができれば良い
  • 引き出した subフィールドで、usersテーブルから持ってこれれば良い
  • 引っ張ってきたuser情報をAuth->setUser()で噛ませれば良い

だと思います。

AppController::initialize() の中身を、こんな感じか・・・?

<?php
public function initialize()
{
// 省略
        $this->loadComponent('Auth', [
            'storage' => 'Memory',
            'authenticate' => [
                'ADmad/JwtAuth.Jwt' => [
                    'key' => /** どこ! **/'', 
                    'userModel' => 'Users',
                    'fields' => [
                        'username' => 'sub'
                    ],
                    'queryDatasource' => true
                ]
            ],
            'unauthorizedRedirect' => false,
            'checkAuthIn' => 'Controller.initialize'
        ]);

ってことで、鍵情報を探しましょう

Firebase Authenticateで利用する「鍵」

これは公式Docに書いてある気がするので、当たってみる。

Verify ID Tokens  |  Firebase

最後に、トークンの kid 要件に対応する秘密鍵によって ID トークンが署名されたことを確認します。https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com から公開鍵を取得し、JWT ライブラリを使用して署名を確認します。該当エンドポイントからのレスポンスの Cache-Control ヘッダーに含まれる max-age の値を使用して、公開鍵を更新する時期を確認します。

これかな・・・?

https://github.com/search?q=https%3A%2F%2Fwww.googleapis.com%2Frobot%2Fv1%2Fmetadata%2Fx509%2Fsecuretoken%40system.gserviceaccount.com&type=Code

ふーむ。

というのと、JWT SDKを見ると「公開鍵のコンテンツを入れろ」であり、パスを指定するんじゃないのか。

f:id:o0h:20190105231743p:plain
vendor/firebase/php-jwt/src/JWT.php

(まぁ、 Security::salt() の値を入れるか?の択一なのだから、そうか)

ということで、一旦、ものすごく雑に

  • オンザフライで鍵情報をセットしてみる
  • それをもとにしてデコードができるかを確かめる

までをスコープとして、話を進めてみる。

やるのが

  1. Authenticate objectに渡す設定の完成
  2. AuthComponentで/api/users/signup.json の解放(非ログイン状態のアクセス許可)
  3. curlで叩く

というもの。

Authenticateの設定

  1. 鍵を入れる
  2. アルゴリズムの設定

をやる必要があった。

最終的に修正したのが、以下。

diff --git a/api/src/Controller/AppController.php b/api/src/Controller/AppController.php
index 8288dfd..f0de2d8 100644
--- a/api/src/Controller/AppController.php
+++ b/api/src/Controller/AppController.php
@@ -16,6 +16,7 @@ namespace App\Controller;

 use Cake\Controller\Controller;
 use Cake\Event\Event;
+use http\Client;

 /**
  * Application Controller
@@ -46,11 +47,15 @@ class AppController extends Controller
         ]);
         $this->loadComponent('Flash');

+        $keys = (new \Cake\Http\Client())
+            ->get('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com')
+            ->getJson();
         $this->loadComponent('Auth', [
             'storage' => 'Memory',
             'authenticate' => [
                 'ADmad/JwtAuth.Jwt' => [
-                    'key' => /** どこ! */ null,
+                    'key' => $keys,
+                    'allowedAlgs' => ['RS256'],
                     'userModel' => 'Users',
                     'fields' => [
                         'username' => 'sub'

/api/users/signup.json の解放

これをしないと、ログインする前にログインを求められる地獄が出来上がりますからね・・
ついでなので、この時点で「デコードされた結果」まで露呈させるようにしちゃおう

diff --git a/api/src/Controller/Api/UsersController.php b/api/src/Controller/Api/UsersController.php
index d3682f0..75f4fa0 100644
--- a/api/src/Controller/Api/UsersController.php
+++ b/api/src/Controller/Api/UsersController.php
@@ -2,15 +2,24 @@

 namespace App\Controller\Api;

+use ADmad\JwtAuth\Auth\JwtAuthenticate;
 use App\Controller\AppController;

 class UsersController extends AppController
 {
+    public function initialize()
+    {
+        parent::initialize();
+        $this->Auth->allow(['add']);
+    }

     public function add()
     {
+        $this->Auth->identify();
+        /** @var JwtAuthenticate $auth */
+        $auth = $this->Auth->getAuthenticate('ADmad/JwtAuth.Jwt');
         $this->set('_serialize', 'data');
-        $this->set('data', ['status' => 'OK']);
+        $this->set('data', $auth->getPayload());
     }

 }

実際にリクエストしてみる

IDトークンのとり方はすでに触れているので割愛するけれど、それを利用して次のような形でアクセスする。

curl -X "POST" "http://localhost:8101/api/users/signup.json" \
     -H 'Authorization: bearer ${実際にブラウザとかでとったIDトークン} ' \
     -H 'Content-Type: application/json'

これで上手くいってると、ちゃ〜んと情報が入ってきた!! f:id:o0h:20190105235353p:plain

もし、ココでそのまま $this->Auth->setUser((array)$auth->getPayload()); としてあげれば、ログイン状態の確立はそのままできちゃいそうかな? *2

clientとつなぐ

では、実際にJS側から叩いてみます。
corsとか面倒くさいので、 github.htmlはもうwebroot/下に置いてしまいましょう。 その上で、次のようなコードを書き加えてみました。

const config = {
    // いつもの
};
firebase.initializeApp(config);

const provider = new firebase.auth.GithubAuthProvider();
firebase.auth().signInWithPopup(provider).then((result) => {
  firebase.auth().currentUser.getIdToken(true).then((idToken) => {
    const xhr = new XMLHttpRequest()
    xhr.open('POST', 'http://localhost:8101/api/users/signup.json')
    xhr.setRequestHeader('Authorization', `Bearer ${idToken}`)
    xhr.send()
  })
}).catch((error) => {
  alert(error.message)
})

これをやると、「勝手にgithubのpopupが開いて」「勝手にsignup.jsonを叩いて」という挙動を起こします。
成功・失敗の見分けがつきにくいのですが、ChromeのNetworkパネルを開いておいてxhrが200を返していたら成功です。
もしくは、Auth::setUser()などに JWTAuthnticateから取得したpayloadをそのまま突っ込んでおくと、ログイン状態が成立しますので、 例えばその後に /users などを開くと、「要ログイン」状態にあったものを突破できます・・・!!

userの登録

あとはもう、なんてことなくて「普通にCakeかくぞ〜」という感じです。

例えば、 add()アクションの内部でこんな感じのメソッドでも呼んであげると良いのでないでしょうか。

<?php
$this->Auth->identify();
$auth = $this->Auth->getAuthenticate('ADmad/JwtAuth.Jwt');
$this->registerUser($auth->getPayload());
<?php
    private function registerUser($auth)
    {
        $user = $this->Users->newEntity([
            'sub' => $auth->sub,
            'provider' => $auth->firebase->sign_in_provider,
            'avatar_url' => $auth->picture,
            'name' => $auth->name,
        ]);

        return $this->Users->saveOrFail($user);
    }

これで usersテーブルへのsubフィールドの探索は実行されるので問題なさそ、という感じです。

残todoとまとめ

処理の流れを掴むため〜という目的の記事だったので、(あの酷いJS部分を差し引いても)実用できるレベルにはありません。
少なくとも、以下の点は必要だと思います。

  1. そもそも既存ユーザーログイン実装してないし!
  2. 取得した情報の、タイムスタンプ他の検証をしていない
  3. ache-Control ヘッダーに含まれる max-age の値を使用して、公開鍵を更新する時期を確認し てないので、非効率だし迷惑

など。まぁ、概念はつかめたと思うので、やってしまえば問題ないかな〜とは思います。

いずれにせよ何にせよ、あの面倒くさいユーザー情報管理があっという間にパワフルに完成するぞ!!すごいぞ!!!!!という気持ちになりました、使うぞ!


できたもの

github.com

*1:cakeの認証オブジェクトについてはhttps://book.cakephp.org/3.0/ja/controllers/components/authentication.html#id12 など

*2:コントローラーの中からAuthenticate Objectにメッセージを直接吐かせるのって、どうなんだろうな〜。他のSocial Loginとかの場合の実装ってどうしてんだろ

Firebase Authenticateに触れてみる①

イントロ

ちょっとした思いつきで、pet projectとして小さなサービス作ってみるか!!というのを昨日の夜に思いついたのですが。
・・年末〜年始でやろう!と思って熱の引いてたネタの焼き直しで。ふと「これなら形にできそうだぞ!」というアイディアが浮かんだので。

で、テーブル設計とかばっと出しつつ、必要な画面を考え始めたところで「ユーザー登録/認証周りどうしようかな〜」っていう・・・
作るだけなら強制ログイン状態を生み出して、まず「動くように作る」をして、そっから考えよ〜!でも良いのだし。そうするつもりだったのですが。

うん、パスワードとかtokenとか預かるの嫌だ!
やるにせよ、何か今風のないのかな〜と。

GitHub/Twitter/Gmail辺りのOAuth連携をやるか、昔ながらのId/Passログインを作るか。ああ、tokenも預かりたくないし、かといって改竄できそうなのも嫌だし、パスワードも預かりたくねぇな〜ていうかソーシャルのアバター使いてぇな〜なんかな〜〜〜

ってことで、Firebase Authenticateを使ってみようと思いたち。

すごい雑なログインシステム(まで

びっくりするくらい簡単に動いてしまって、まだびっくりしてます。

  1. まず プロジェクトを作る https://console.firebase.google.com/
  2. Authentication -> sign-in method で(とりあえず) email/passを有効にする f:id:o0h:20190105170721p:plain
  3. コンソールのPJ概要にあるスニペットとウェブサイトで Firebase Authentication を使ってみる  |  Firebase を参考にいじる f:id:o0h:20190105171203p:plain

で、出来上がったのが下のようなもの。

すごく雑なFirebaseAuthenticateログインフォーム · GitHub

・・・これだけでちゃんと動くっていう。。 驚きがありませんか。

やりたいことを整理する

モチベーションは「ログイン・認証機構をこっちで作りたくないし、秘密情報をこっちのストレージにおいておきたくない」です。
こんな我儘を実現するシステムとは・・・

  1. エンドユーザーの操作によって、3rd partyのプロバイダに対して、「有効なアカウント」のお墨付きをもらってくる
  2. エンドユーザーから、こちらのサーバーに「安全で安心な認証済み情報」が送られてくる
  3. こちらのサーバー上にあるユーザーデータと紐付けて、ログイン処理を実行する

このあたり、なんとなくJWTとか使ってみたら行けるのかな〜どうかな〜って思ってるんですけどね。使ったことないので。試してみる。

ココらへんを読み解けば良いかな?

fireabase的には firebase.auth().currentUser.getIdToken()で「トークン」がとれる、と。
この値をhttps://jwt.io/ でデバッガに渡してみると、認証情報が入ってるっぽい。

f:id:o0h:20190105174459p:plain

このuidの値から、こちらのユーザーデータに対して同一性を手繰り寄せて行けばよいのかな?
やってることはコレっぽいな。
Firebase Auth のユーザ認証機能を自前のデータベースと連携する - Qiita 「Firebase Admin SDKを使うか、JWTライブラリを使うか」という、正にドキュメント上で言及されていた分岐があるだけ〜という感じがする。

どっちも内容は同じな気がしていて、「Firebase Admin SDKを利用するメリットはなにか」というと、ドキュメントに書かれているこの辺りか。

提供された ID トークンが正しい形式で、期限切れではなく、適切に署名されていれば(デコードされた情報を取得できます)

逆にJWTについての但し書きは

(利用するためには)ID トークンのヘッダー、ペイロード、署名を確認します。

となっている。

つまり、JWT実装した場合は自前で検証をしなければならないーと。

先に貼った「CakeでJWT認証やってみよう」の記事においては 10. Testing JWT Authentication の部分に当たるかな?

ということで、やることとしては

  1. client側では、Firebase Authenticateを通ったあとに「トークン」をこっちのサーバーに投げる
  2. こっちのサーバーでは、投げられたトークンを検証する
  3. 検証に通ったらアプリケーション上でのユーザー同定・ログイン処理を行う

ができりゃ、いーかなぁ

実際に「外部プロバイダ」でログインやってみる with GitHub

大体の処理の流れは掴んだ?と思うので。あとは理屈で覚えるより動かしながら眺めてみる。

ってことで、GitHubログインをやってみたい

JavaScript を使用して GitHub で認証する  |  Firebase

  • まずは sign-in method でGitHubを有効にして f:id:o0h:20190105175810p:plain
  • callback urlを確認して
  • GitHubの認証appを新規作成して https://github.com/settings/applications/new
  • さっき確認したcallback urlを参考にしつつ必要な情報を埋めて
  • 登録するとアプリケーション詳細ページに飛ぶのでClient ID/secretを確認して
  • Firebase consoleに戻って、ID/secretを記入する

サービス側の設定はこれでOK。
ココらへんから「HTMLファイルを直開き」だと動かなくなってくる(http:// or https:// で始める必要がある)ので、必要に応じて firebase serveコマンドなどを用いてローカルサーバーを利用する

先のドキュメントを参考にして、スクリプトを書いていく ・・・いちおうgistにも貼ってはいるけれど、これだけで動いちゃう・・・

const provider = new firebase.auth.GithubAuthProvider();
firebase.auth().signInWithPopup(provider).then((result) => {
  console.log(result.user)
}).catch((error) => {
  alert(error.message)
})

f:id:o0h:20190105184503g:plain

意味わからないですね簡単すぎて・・・・

ということで、あとはサーバーサイドの実装。

簡単なusersテーブル付きのログインできるcake appを書く

もう、べったり先のブログを参考にして。 プラグイン使ってJWT認証どのくらい簡単にできるかな?に挑みます。

github.com

あ、でもCRUDプラグインはいいや。

ココらへん使いつつ、Docker環境用意してcakephp/appのproject作って〜っていうのはいつもの通りなので省略。

1. cakephp-jwt-authプラグインの設置

このように

# composer require admad/cakephp-jwt-auth
Using version ^2.3 for admad/cakephp-jwt-auth
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
  - Installing firebase/php-jwt (v5.0.0): Downloading (100%)
  - Installing admad/cakephp-jwt-auth (2.3.2): Downloading (100%)
Writing lock file
Generating autoload files
> Cake\Composer\Installer\PluginInstaller::postAutoloadDump
/app # bin/cake plugin load ADmad/JwtAuth

/app/src/Application.php modified

2. usersテーブルの設置

親切にMigrationファイルのサンプルとか用意してくれてるの。すげー

users.php

で、ちょっと内容を変えたいので(icon urlとか足してみたい)、参考にしつつ少しいじる

  1. bin/cake bake migration CreateUsers して
  2. こんな感じの内容を(下記
  3. bin/cake migrations migrate する
<?php
use Migrations\AbstractMigration;

class CreateUsers extends AbstractMigration
{
    /**
     * Change Method.
     *
     * More information on this method is available here:
     * http://docs.phinx.org/en/latest/migrations.html#the-change-method
     * @return void
     */
    public function change()
    {
        $this->table('users')
            ->addColumn('name', 'string', [
                'default' => null,
                'limit' => 64,
                'null' => false
            ])
            ->addColumn('avatar_url', 'string', [
                'default' => null,
                'limit' => 256,
                'null' => false
            ])
            ->addColumn('created', 'timestamp', [
                'default' => 'CURRENT_TIMESTAMP',
                'limit' => null,
                'null' => false
            ])
            ->addColumn('modified', 'datetime', [
                'default' => null,
                'limit' => null,
                'null' => true
            ])
            ->create();
    }
}

3. users周りのコードをザクッと

bin/cake bake all Users しておく。
いろいろなファイルが出来上がる。

この時点で、 /users/add とかを見ると、画面ができているよね f:id:o0h:20190105202838p:plain

4. サインイン用のAPIエンドポイント

ここからが本番!って感じ。

tokenを投げてユーザー登録をする先 が必要になるので。

/api/user/signup.json とでもしよう。

routes.phpに下記を追記

<?php
Router::scope('/', function (RouteBuilder $routes) {
    // 省略
    $routes->prefix('api', function (RouteBuilder $routes) {
        $routes->setExtensions(['json']);
        $routes->post('/users/signup', ['controller' => 'Users', 'action' => 'add', 'prefix' => 'api']);
    });

src/Controller/Api ディレクトリを新設し、以下のように UsersController を作成

<?php

namespace App\Controller\Api;

use App\Controller\AppController;
use Cake\Http\Exception\NotImplementedException;

class UsersController extends AppController
{

    public function add()
    {
        throw new NotImplementedException('これからね!');
        
    }
}

この時点で、一旦routes.phpから $routes->applyMiddleware('csrf'); をコメントアウトしておいて、こんな感じのcurlを打ってみる

curl -X "POST" "http://localhost:8101/api/users/signup.json" -H 'Content-Type: application/json; charset=utf-8'

これで status:ok といったレスポンスが返ってくれば、routingはこれでOK!

5. Enabling JWT Authentication

とのことなのだけど・・・はて・・・

長くなってきそうなので、一旦ここまで!

次にやることは

  1. cakephp-jwt-auth の仕事や中身について理解する
  2. 「How to add JWT Authentication to a CakePHP 3 REST API」で説明されているシナリオについて理解する
  3. 実際にFirebase Authenticateを利用したGItHubログインを実装してみる

で!

daisuki.nichiyoubi.land

会社の忘年会でコンテンツ作成手伝いでサービスを作ったら面白かったよ、という話

師走

本当に常に寝不足というか、寝るかねないか?の判断を毎晩迫られているような日々が続いておったわけですが、一段落、開放感、よかった・・・という訳です。
特に今週1週間(火曜〜金曜。月曜は祝日でしたので)、もしかしたら、人生で1番寝てなかったかもしれないってレベル。 アドレナリン出しまくってた気がする。

「師走ぢゃなくて、これぢゃ死走だょ〜ぅ!」みたいな謎の声が頭の中で聞こえてきたときは、悲しい気持ちになった。

社内忘年会の賑やかしを依頼されまして

企画運営チームの人から突如呼び出されて、「なんかエンジニアリングで面白いことやっちゃってよ!!」みたいな依頼をされまして、あーなるほど〜そういうの面白いよね〜って食い気味に乗っかり。

「サービス」相当のものを作る、みたいな方向に

「実際にみんなが操作をして、それでなにかが起こる」みたいなの物を用意することに。そっちの方が面白いから。

何をするって指示とかがあったわけじゃないし、過去に作ったものをそのまま使うという選択肢も大いにあったし。実際、案として「SlackとかSNSとかのデータを取ってきて、何かクイズみたいなのを作るとか」みたいな、作ろ〜っていう感じよりも支援よりの案も出したりしてた。

結果的には「モバイルクライアントアプリ、データベース、管理用バックヤードアプリ、データ集計バッチ、集計結果プレゼンテーション出力機能」を備えるような、1つのサービスが出来上がりました。

どのくらいで作れる?

基本的に「業務に差し支えの無いように」というのは懸念してくださっていて、だから「無理なくできるくらいの人日で、やれる範囲のことを。支出日数を決めたら、こっちから上長に通す」という風に言われ。

すごい有り難いし楽しそうな話だし、とはいえ日数が限られていて年末までのタイムラインでの業務も現実的に設定されている、これは譲歩を引き出せても「3・・・いや、2日ちょいくらいが限界かな・・」という風に結論づけ。

2日で(クローズドとはいえ)丸々1つのサービスを作りましょう

「言われた分の通常の業務時間をまるまる使って良い」と。

2日間っていうのは、「年末の残り3週間のうちから支出する」にはものすごく大きく、素敵なサービスを作りきるには結構小さい・・・・という位のバランスだなぁ、と感じるところ。

ハッカソンとかの「本当に2日間フルコミットできる」とは違って、言うて出勤して社内にいてSlackメンションとかMTGとかも入ってくる。
そういう2日間。

となると、完全に「いかにスピードを上げて作りきれるか」みたいなチャレンジとなる訳です

やってみたら出来た

日数が限られていて、でもあまりにも中途半端になっては仕方ない、ってことで自ずと「めちゃくちゃ早く進める」方向に思考が向く。無駄なことは捨てるし、使えるものは使い切る。

請け負ったのは自分ともう1人で、こちらはCakePHPならスラスラ書ける、あちらはJSをスラスラ書ける、ってことで。「お互いに得意なことにフォーカスして最速でやろうぜ」みたいな話になり、フロントエンド/バックエンドの実装詳細とか設計とか、なんなら「どういう機能があればいいっけ」レベルの話もほとんどせず、「こんな感じで作ってみた」ていうのを形にしてから共有するような進め方に。

ちなみに、一緒にやってくれた人の感想。 medium.com

・・・考えてみたら、よくコレでうまくいったな。まぁ、不安とか未知性とか考えている時間が全くなかったんだけど。そもそも。

たぶん、結果としても良かったし、「成功」なんだと思う

「結果オーライ」みたいな立場に立つと、忘年会参加者が凄いワイワイ使ってくれたし、社長や(誘ってくれた)幹事チームからも「めっちゃ良かった!」という声を実際にいただけたので、成功。
自分としての納得感もあるし。

自分の「初期衝動」に還ってくる部分

やってみて、単純に「面白そうなものを作るの面白いよね」とか「自分のアイディアを自ら形にしていくの楽しいよね」といった感想がまずあって。

これは、学生時代に自分が初めて「PHP」とか「サーバーにアップロード」とかに手を出した時の、初期衝動と同じで。今プログラマやってるのも、そういうのに楽しさを感じるからだな〜って思うし、今回の企画は随分とそれが満たされた。
特に、自分のアウトプットを、実際に使ってもらったり楽しんでもらったり〜っていうのを見るのは誇らしい瞬間。それが目の前で起きてるんだから、そりゃテンション上がるよねー

「成長実感」も還ってきた

「まぁ我々2名なら、この程度は2日ありゃ行けるな?」という煽りをしたのは私ではあるのですが。。
たぶん、参加してくれた人たちには「2日だけでこんなに!」って言ってもらえるような気はしてる。

これを実現できたのは、プログラミングを初めてから今までの積み重ねがあるからなんだよな〜って思わされた。

appどうやって作る?CakePHPならパパっと使える 環境構築しないとね?Docker使える 公開とかデプロイの設定←この前書いたHeroku Container Registryでいいや データ管理とか更新同期どうしよ←Firestore DB楽しかったよ プロジェクト立ち上げ時って、色々と開発効率落ちるよね←PhpStormの設定とかはもう慣れた

みたいな。

いろいろな事をショートカットしまくって、本質的な「機能を作る」「快適にコードを書く」の部分まで足早に進めたなぁ〜というところは、自身の成長の1つだよねと。

あとは、今回はじめて使ったライブラリやSDK/APIもあるわけだけど、そういうときに「パッとググれる」「動かしながら理解する」という能力も、これまでの経験によって裏付けられるものなんだと思う。

「強くてニューゲーム」を何度もしたい

「ゼロからのスタート」であるのは、それこそ8年前の「はじめてのWordpressをインストール」した時も今回の「mkdirしてgit initから」をした時も全く同じはずで。
同じことを火力高めでやれているのが、なんだか「強くてニューゲーム」感があって、楽しいな〜と。

「1人Advent今の自分なら行ける!」と思ってやってみたのも、ある意味「強くてニューゲーム」だな。

あんま知っていることばっかやっていたら飽きるし鈍ると思うんだけど、「何かを作る」というのはそれ自体に刺激や意欲向上につながるような成分は多いと思うし、ちょいちょいやっていけると良いのかも知れないぞーーーって思ったのが、今回の学びになりました ☺

1人Advent最終日

1人AdventのDay- 25 です。やった〜

adventar.org

1人Advent Calendar戦績

カレンダーを全部埋めることに成功しました・・・

f:id:o0h:20181226032844p:plain
そして、このエントリーが最後の1マスに・・

  1. daisuki.nichiyoubi.land
  2. cake.nichiyoubi.land
  3. daisuki.nichiyoubi.land
  4. daisuki.nichiyoubi.land
  5. daisuki.nichiyoubi.land
  6. daisuki.nichiyoubi.land
  7. daisuki.nichiyoubi.land
  8. cake.nichiyoubi.land
  9. cake.nichiyoubi.land
  10. daisuki.nichiyoubi.land
  11. daisuki.nichiyoubi.land
  12. daisuki.nichiyoubi.land
  13. cake.nichiyoubi.land
  14. cake.nichiyoubi.land
  15. daisuki.nichiyoubi.land
  16. cake.nichiyoubi.land
  17. daisuki.nichiyoubi.land
  18. daisuki.nichiyoubi.land
  19. cake.nichiyoubi.land
  20. daisuki.nichiyoubi.land
  21. cake.nichiyoubi.land
  22. cake.nichiyoubi.land
  23. cake.nichiyoubi.land
  24. cake.nichiyoubi.land

改めて、こうやって並べてみると感慨深いというか。長かった。

1番時間かかって、1番「やべー楽しーー」ってなってたのはコルーチンのやつ。

何でやり始めたか

最初の記事に書いたとおりですけど、まぁやる前は「あんまり大きい口叩けない・・」という心理もあったのですが。
今なら「いくらでも調子に乗れる」と思い、解放された気持ちで少し整理してみますと。

個人的に、今年の1年を通じて 「自分がやってきたことや、やれることに対して、ふわふわっとした自信のようなものはあるが、(とりわけ会社などの身内に対して)ふんぞり返っているだけのような、エゴの増長だけがある」というような感覚があり。そういうのは誰しもあるのだとは思うのだけど、今年はとりわけひどかった。
結局のところ「強くなっていそうな感覚はある」のに「本当に強くなっているのかが分からない」という渇きであり、じゃあ「今までにやってないよーな、"明確にハードルが高そうなこと"を1つ・・やってみるか!?」といった動機でした。
自分の等身大の知識や思考を「削り出し」するような形で、25本の記事にまとめる。そのくらいなら"今なら"できるのでは?と思ったので。

ということで、何かしらの挑戦的な意識であり、自罰的な追い込みであり、とりあえず「量」と「テンポ」でやってみよ〜なAdvent Calendarでございました。

何となくこだわったこと

最初は、「よ〜し時間があるときに書き溜めておこ!」と思っていたのですが、とあるツイートを見かけて「書き溜めやクロスポストなんて、私はしない」といった旨のことを言っており。
ああ・・・やるなら・・・俺も邪道に落ちずに・・・・・堂々と・・・「やりきった」と・・・・言ってみてぇ・・・

という悪魔に取り憑かれていました。
ので、それは貫徹。ただ家に帰るのが大体25時前とかですし、「当日24時まで」縛りは一切してないです。私が寝たところが日付変更線だ

会社のやつを2、CakePHPのものを2本とそれぞれやりきった上で1人Advent25。

やってみてどうだったの

最初の方

開始3日目くらいで、「あ。いつもの会社のブログや自分の(行動主導でなく)テーマ主導で書くようなノリと同じ気持ちでいると、相当つらい」というヤバみを感じます。
なんか おもっていた のより たいへん そう

そのモヤモヤが深淵に落ちていって、取り組み方として「修行のようだ」と思い始めてきた時のツイート

まぁそれでも、2本、3本と書いていくと「途切れさせたくない」という気持ちも芽生えてくるし、そういう「やってる感」を得られていくと、良いもんですね。

中盤戦

この記事から、Cake縛りブログの方はフォーマットができて来てよかった。 ずいぶんと昔(この辺からの動き)に作ったCake縛りブログの品数を拡充しよう、というのも今回のAdventで狙っていた事柄の1つだったので、これは全体を通じて得られたもののなかでも特筆したいポイントの1つ。

あと、結局エントリーを1本上げるのに油断すると2時間とか調べながらやって3時間とかかかっていく中で、「睡眠時間削れまくる・・・」というのに絶望し始めてきたり。
「毎日やる」「習慣としてやる」ような話は、何より生活リズムに組み込んで、例えば朝起きてシャワーを浴びる様な「取り掛かって当たり前」みたいなビルトインをしないといけない。そう思います。

第4週

個人的な他の事柄と相まって、17日の週からがマジで辛かった・・連日、「とにかくチョロくかける記事はないか!?」とネタを探していた気がします。ローカルにあったDockerファイルを転写しただけの「ギリギリ形になるかな!?」という内容を繰り出したり、Cake3.7.1キタ!何も考えずにかけそう!!と歓喜したり*1
やり始める前や、最初の数日くらいは「25本出すぞ、それだけでいい!いのちを大事に!」みたいな気持ちでいたのに、結局「とにかく書けばいいから・・・」って状態にまで陥ったのは、多分このあたり。なんというか「命からがら」って気持ちだった。

あとは、この辺りからだんだんと「本を読んだり・・・ペットプロジェクト動かしたり・・・あれしたり・・それやったり・・・・したいな・・・」って感情がたくさん湧いてきて、「あ、なるほど、今は抑圧されてるんだな」と悲しい気持ちを自覚していったりしていました。

終盤

その第4週の低調から、華金+忘年会+システムトラブル+深夜対応of徹夜・・みたいなのが続いて連休中が最悪になってた。
ここまで、少なくとも「翌日朝*2までに出す」は死守できていたのに、MPカラカラ過ぎて22日のエントリー更新がめっちゃ遅れた。悲しい。
連休中にどうにか追いついて、「最終日は振り返り記事だけ書けばいいぞ!!」という状態にどうにか持っていき、今日に至る。

そうか・・・今日に至ったのか・・・・・・・

Adventやってみてどうだったの

当初の予定より、「CakePHP中心」ではなかったような。
まず前提として、自分は「Cake系の発信はもっとしていきたいな、自分の経験はコミュニティに還元できる部分もあるだろうしな」という感覚は強く。なので、このAdventもそれをするつもりだったのですが。
まぁ、別に「そうじゃないことをやっていた」のは何も悪いとは思ってないけども、何となく、蓋を開けてみてこの結果〜というのが自分的には少し意外でした。

さて。

整ったもの

Hugo + GitHub Pagesでのモノ書き環境が整った。これは地味に成果なのです。

  • VS codeでMarkdownの編集&Git commit/pushをサクサクっと
  • Hugoのarchtype(テンプレート)を整えた
  • Cake縛りブログのテンプレートを確立できた
    • 先述の通り!
  • 「コードリーディングをしてその内容をそのまま書く」みたいなやり方は、アリだと思った
    • 何だかんだコードリーディングは力になる
    • それをテキストや言語化する時の学習性よ

(Adventと関係なく)12月に新しく1つ「書く場所」を増やした*3のと関連して、ずいぶんと「markdownで書く→pushする」みたいな動作が洗練された。

ちゃんと、何かしたくなったときの武器 = 億劫でないレベルのハードルの低さを備えた選択肢になったな、と。

身を絞りきってみたらアウトプットできることはあるんじゃないの

という感覚を、改めて得ました。
頭では、例えば「普段コードレビューで指摘した内容や示した補足など、そういうのを体裁整えてあげるだけでブログ記事になるんじゃないの?」という考えを持っています。
が、それを「テンションが上ったときだけでなく、毎日毎日やる」というのは、出汁でもとられているような、自分の身を削るような思いもありました。
そして、「そのくらいすればまぁ何かあるよ」というのを体験できたのは、自分にとって新しいゾーンでした。

普段は、ここまで「書くこと」が純粋に目的化するような状態はないので。
先程触れた「修行のような感じ」というのは、この「搾り取られる」という部分から来ています。

毎日毎日毎日・・・

「やらなきゃいけないこと」って、だいたい先延ばしにするじゃないですか。俺の魂がやる気になるのを待つ。まぁ明日やろう、週末にひっくり返そう、とか。
「毎日やる」って決めると、すごいんですね!1つ先延ばしにすると、即借金。明日サボると明後日の負荷が200%。やばい、コレは非常に逃げられない。
今年は、個人的に会社のブログの方で「月1本くらいは書こうかな」をやっていたんですが、(こちらは全然雑にやっつけてるとはいっても)25連続で来るんですよ。

1ヵ月弱を通じて、ひどい体験をしたなぁ・・・・

ただ、マゾ的な快感も一部はあると思って、この「習慣」ってやつはいいなーと思いました。自分を強くしてくれそう。

有言実行が重い

別に誰かが見てたとは思ってないのだけど・・・Adventarに入れて、「やってみるぞ!」と放言してしまった以上、「やらなきゃいけないやらなきゃいけないやらなきゃいけないやらなきゃいけない」って完全に自分で自分の首を絞めていた。
しんど・・

ただ、「やって当たり前」をちゃんとやるのは、やはり「良い」と思うので、自分の中での真っ当さみたいなとこに水を与えていくような感覚は良かった。

総論

年の瀬に、ちょっとした「やりきった感」を自ら生み出すことができたんじゃないかな〜と思います。
最後の最後で公開おくれたりとか、深夜から始めて雑なままになったりとか、とにかく「自分の理想としていたものからかけ離れてる」とか「何でこんなにうまくいかないのか」とかの嫌悪感はあるものの。
それでも、カタチとして「25記事作る」は主観客観どちらに立ってもブレずに「25本」のままそこにあるので、達成はしたんです。否定のしようのない成果というのは良い。

この先どーすんべ

「たまたまノリでやってみましたww」っていうだけのAdvent Calendarが、なにか自分を明確に1つ上のステージに押し上げてくれたりだとか、新しい扉が開くだとか、別にそういうものではないと思うのですが。
ただ、「ちゃんと続けること」という軌道を作り出せてハイになっているところは、今後もスイッチを入れて置けるとよいな〜と思います。せっかくなら。

  • やるぞ!ということ
    • Cake縛りブログはもうちょいどうにかする、まずは50本くらいは記事入れたいなぁ
    • 直近の話でいうと、年末年始の休暇で「手を動かす」系の積読をつぶしておきたい。今の自分は心身的に「無理に向き合う」耐性が一時的に上がっているはずなので。具体的には次の2つのいずれか、もしくは両方
      • 7つの言語7つの世界
      • Kotlin Webアプリケーション
  • やってみたいこと
    • なんかコード書きたい〜〜〜我慢してる〜〜〜〜〜〜〜もう我慢しなくていいんだ〜〜
  • やれるのかな・・・やれるとかっこいいぞ・・
    • いい加減にxUTPを読み進めていきたいのです
    • やるかな・・・ xutp.nichiyoubi.land か・・・・?*4

ということで!おわり!
メリークリスマス〜〜〜〜〜〜

*1:それでも、関連PRさぐってコード読んでissueよんで〜ってやるのは時間そこそこ要るんですが。久しぶりにやったら、「あ、コレ思ったより辛い」て感想

*2:朝とは?人による

*3:http://daisuki.nichiyoubi.land/entry/2018/12/04/183259

*4:レポジトリはずっと前からあるんだ https://github.com/o0h/reading-xutp