#phperkaigi 楽しかった!(全部出たかった、悲しい)

#phperkaigi に行ってまいりました!!

phperkaigi.jp

f:id:o0h:20190402082152j:plain
じゃーん!パーカー
f:id:o0h:20190402082104j:plain
じゃじゃーん!スピーカー!!

(パーカーに印刷されている内容読んでなかった、写真撮りながら気づいて笑ったw)

一言で感想を言えば「楽しかった!」「スタッフの方々はじめ、皆様とても素敵な時間をありがとうございました!!」に尽きるのですが、それでまとめると全てが終わってしまうので、ウダウダと書いてみたいと思います

↓↓ありがたや qiita.com

LTしたよ

大きめのカンファレンス〜ってそもそも参加もそんなにしていない(#phpconfukとか行ったくらい)のですが、最近は生きていると不安になるので「なにか分かりやすい"やったこと"ほしいよね〜」「あぁコミュニティに対して貢献とかしたいわね!!!」て気持ちもあり。1人Adventと同じ流れ。

で、CfPをバババッと打った次第。

といっても、この時は明確に何かを話すぞ!!という自分をイメージはしておらず、どちらかというと「CfP出しまくってみよwww」くらいのノリでした。*1

当初は「1日1CfP出そ!」くらいに思っていたのですが、ぜんぜん書いてませんね!
で、びっくりしたことに、1つ採択していただきましたので🎉

わ〜しゃべるんだ〜わわ〜〜〜

そんな感じでご縁があり、スピーカー参加ですよ!やったね!

進捗大事

あんなにたのしみにしていたのにおしごとのしんちょくがだめすぎてなにもみれなかったかなしい

もうずっと<※検閲削除>みたいな過ごし方を3月はしていたので、あんなに楽しみにしていたのに、練馬に着いたのが最終日の14時過ぎです。

そして(寝不足は仕方ないにしても)少しでもちゃんと喋るためにコンディションを整えておきたい・・・と思い、ここからコンビニに寄って小腹を満たしてから受付です。 (おそらく行くのが遅すぎて)スタッフのhamacoさんに心配までされてしまう体たらく。。

はい。

ちなみになんですが、こんな最後の最後にやってくるような人間に対しても、スタッフの方がちゃんと受付に待機してくれていたし優しくご案内いただいて有難かったです。

参加の感想、聞いたものの感想など

ということで、LTと徳丸先生の「答え合わせ」とクロージング(&懇親会LT)しか聴けていない😭

ただ、聴けたやつは楽しくて最高味がありました!

ルーキー枠って良い仕掛けですよね。
「カンファレンス怖いならスピーカーで出ればいいじゃない」派もいる中で、カンファレンスの趣旨とあわせても最高のソリューションすぎる・・・!と感じた次第。

その他にも、クロージングでの委員長スピーチを聞きながら、イベント全体の仕掛けが「わ〜〜めっちゃ考えられてるすごいな!!」ってメッチャ感心?感動?していました。
ここ1,2年、規模が何桁も違いますが社内で内輪の「イベント」を立案したり実行したりしている身として、全員を「参加」させられたらいいよね〜工夫がいるよね〜〜は感じているのですが、phperチャレンジすげぇ!!ってなりました。それは確かに楽しそうだ〜と。
実際、前のめりに参加する人がめっちゃ前のめってる!!という現象も巻き起きていたし。徳丸先生の企画は社会的意義もあるし、あと「システムがあったら攻撃してみたい」はやっぱりwebエンジニアとして自然な感情ですもんね・・・!

トーク(LT)の内容も見て・感じるものがあったので、触れておきたいのですが、長くなりそう&とりとめなくなりそうなので。。
「参加者フィードバック」は見たものは全部送りました!!!フィードバック集める仕組みあるのめっちゃ嬉しいですよね、自分にもなにか来るのかな、どきどき。

とりあえず「生徳丸先生が面白すぎた」「うずらさんの懇親会LTのパワーやばい」「そーだいさんの懇親会LTのメソッドが天才的すぎて・・」辺りは現地で生で見られてよかったなーですw

これだけは言及しておかないと:

って思いつつ、1個だけリアクションを。

某勉強会 における私の過去の振る舞いが多大なるストレスをおかけしていたようで、大変な申し訳無さ・・・・😇

(ちなみに、このレスをしたときはまさか本当に自分だと思ってなかった・・・ってくらい自覚がないw)

ここからまさかり 「マサカリ」って人それぞれなもんで*2*3、そうか〜アレをそう感じる人もいるのか〜と率直に気付かされた。
今後は「自分がやりたいから喋るんじゃ!!」はもちろん意識しつつも、質問してくれる人・質問はしないけど聞いてくれている人から時間をもらっているのも間違いないので、みんながニコニコすっきり出来るように意識できるとめっちゃいいな!!という感想。誠実さ〜みたいなのは客観的にそうであると認めれない限り、意味がないですものな。

質疑応答で上手く答えたいな〜ってのは確かにありつつ、その難しさは「自分の知識量」以上に「相手の質問意図やコンテキストを知らない中で、どこまで補足説明をした上で回答すればいいか?」みたいな火力調整みたいなとこがデカいかなーという気はするので、それは確かにハックできる余地がありそう。

「未熟力」みたいなものを持ち込んで、場全体を良い方向に仕上げることは出来るのかもな〜。

客観的にというか、「自分の話」を度外視して感想を述べれば、こういうLTが出てくる事自体がとてもいいな〜〜ナイスーー!って思うのと、そして、それを壇上に上げてくるのがイベントとして象徴的な気がしておりました。 ええ話や〜

喋ってみた感想

LTした際の雰囲気がめっちゃくちゃよくて最高でした。
練習不足・準備不足もあって周りを見ながらしゃべる〜って余裕こそなかったのですが、でも緊張はしなかったなぁ。あれはイベントの空気づくりによる成功と、MCの手腕によるところが大きそう。空気めっちゃ温かかった。すごい。

5分overしたのはゴメンナサイしかないです・・!(けど、それすらネタっぽく料理していただいて、何だあれ感動する。すごい)

LTとかトークテーマの作り方、なんとなく「知識的なもの(ライブラリの使い方とか)」「経験的なもの(実録とかプラクティスとか)」「思想・感情的なもの(エモ系とか歴史モノとか)」に分けられるのかなーと思っていて、自分がやると「知識的なもの」をちゃんとやるのが1番苦手で。ある意味、誰が話しても同じになるようなやーつではあるので、キーメッセージを見出しにくいというか。
なのだけど、なんとなくそれっぽく形にはできたのかな?よかったー!!って思っています。一安心。
フィードバック開けてみたら全く逆の結果になっている可能性もある。

場の雰囲気に助けられた面は本当に大きいなぁ、アレでダダ滑りしたらまた見ている誰かが居た堪れなく感じていた可能性w

内容としては、

って面があるなー。
chronos使っていると「まぁcarbon使わなくて良さそうじゃない?」があるのと、そもそもCake使いだからchronos(系統)になるわけですが、carbon今どうなってますか教えて〜どっかで調べてみようかな。

思ったこと

githubとtwitterのアイコンを揃えておけばよかった!
というか、githubのアイコンは思い入れがありつつ、twのdisplay nameも気に入っていつつ、アイコンは適当なんだよなぁ。
黒くて丸いアイコンの視認性が悪いし被る上に、他のとこで使っているアバターとズレるのが何も得ないやっていうw

あと、今度はLTだけでなくてレギュラートークできるような、そんな人間になりたい!

心残り

いやーーーー居たのが一部過ぎて、懇親会でお話したかった人と数名お話できたのは良かったけど、これは参加したっていうには足りなすぎる!!!
もっと骨の髄まで楽しみたかった。
結果として、スピーカーとしてチケット代を負担していただいていたのに、それを反故にしている事実もあるわけで。 その(今回のイベントを実行してくれた皆さんに対する)不誠実みたいなのは返上できるならしたい。。

ので!

まず「ちゃんと配分してイベント行ける時間を作っておかないとだった」という反省の念があり、そして「せっかく枠を用意してくれたのに申し訳ない」があり、何より「もっと!楽しみたかった〜〜!」があり。

イベント直後につぶやいていたのが↓ですね。

で、結果から言って

f:id:o0h:20190402102849p:plain こうなりました。

phpconfukは以前に行って(その時もコンディション悪くてフラフラ歩いてた)、超良かったので、行きたいのですが、出す出さない迷っている間にトーク応募がもう終わっちゃってるから・・・ せっかくならスピーカーで行った方が楽しそうだな〜って気がするので、proposal出しまくろうと思います。
(=ちゃんとアウトプットできるネタを増やす、という意味でもある)

初めて参加した勉強会がPHP Matsuriで(楽しかったなー。またやらないのかな。あるならスタッフやりたいレベル)、その時に食べた海鮮丼が私のhatena/GitHubのavatarです。

ということで、これが恩返しなのかわからないですが、超曲解すると「コミュニティへの対しての不義はコミュニティへ返す」のが筋かなと思っているので、
phperkaigiへのゴメンナサイを試される大地に持ち込みます!

まとめ

楽しかった! スタッフの方々はじめ、皆様とても素敵な時間をありがとうございました!!

*1:ティザーサイトのノリに当てられた!というのもあるw祭りじゃ〜って気がした

*2:件の場における被マサカリ当事者として、「あのくらいならまぁ攻撃も悪意も感じておらず」なのですが・・ていうか恐らく普段の自分が発するコミュニケーションの方が十分に強いので😇😇

*3:別の意識として、自分は幾度も「心理的安全性を盾に言葉狩りをしていないか」みたいな方に強く疑問を抱くことがあり、発言する側・受け取る側・見ている側が相応に精神的に強くなる必要はあるよねーみたいなのもあり

やったこと・書いたもの{2019,03}

会社ブログ

tech.connehito.com

中の人にもご反応をいただいて、コレが個人的にはすっごく嬉しかった。ありがとうございます

tech.connehito.com

社内LT

いや、全然LTじゃない。ぷちWS的なデモ的なの含めつつ、質疑応答をはさみつつとはいえ、50分位ぶっ通しで喋った。

OSS

github.com rectorめっちゃ良い子ですよね。。

勉強会・LT

fortee.jp

うわ〜〜〜〜5分LTだけど大きい勉強会初めて〜〜〜楽しかった〜〜もっと上手く喋りてぇ! 感想とか振り返りは個別に書きます!

その他

dailyportalz.jp

2019年の目標の1つだった「デイリーポータルZに貢献する」を達成しました!!祝

丁寧に言語化をする

人が成長する瞬間、あるいは「今得られる成長を最大化するやり方」みたいなのはいくつかあると思うのだけど。

例えば、環境が変わった瞬間。これは大いに伸びる。
中高の部活など顕著な例だろう。「今までに全く見知りしなかった仲間に囲まれ練習をする」とは、下手したらその1ヶ月だけで、それまでの数倍くらいの威力があるのでは。
転職もそう。新しい会社に入って、流石に思春期に得る部活動でのそれとは伸び代が違えど、「異なる面子」「異なるルール」に囲まれながら過ごす新鮮で刺激的な期間は、自身の本質を相対化し研ぎ澄ませる。

これらは、自分においても大いに実感がある。学生インターンでプログラマを始めた瞬間、その会社で(結果的にそのまま新社会人になっても同じポジションで続投した)先輩ポジションの人も社内にいなくなり1人webアプリ開発チームみたいな状況、そして転職して「先輩がいるチームで既存のプロダクトをやる」状況。
全て自分にとってターニングポイントだったとはっきり言える。

もう1つ、これは「0→1を得る」ではないが「1を10にする」ベクトルで、何であれ「丁寧に言語化をする」というのはあるのではないか。
エンジニア職であれば、コードレビューなど絶好の機会であろう。あるいは、社内外の勉強会でLTをする。これらは、「新しい知識を手に入れる」ものではないものの、自身のスキルやセンスをメタから顕在へと引きずり落とす・・・みたいな意味で、学術の深化・定着の文脈で非常に大きな成長ポテンシャルを引き出す!と思う。
残念かな、「外部の刺激を拡大する」のではなく「自身の領域の密度を上げる」ためのムーブとなるので、頭打ちもあれば自動的に成り立つものでもないが。

そんなことを、今日、自分が投げたprに対して質問を得ながら考えた。
それは「使い方は分かったけど、これまでのやり方と比べた際の利点をどう考えているか?」というものだった。ここで、「今までと何も全く変わらないが、最近ハマっているので!!」などと答えれば、それはチームに対する不遜であろう。対して、しっかりと「何となく」を形式知化する努力を達成すれば、それは自身の内においても「説明可能な知識」となるわけで。そして、「説明可能」同士は、少なくともそうでないものより、遥かに効率的であり意義深きまのになるわけで。

やったこと・書いたもの{2019,02}

会社ブログ

tech.connehito.com

社内LT

OSS

勉強会・LT

その他

昨年の下半期で結構ずっとやってたやつが、やっと・・・

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

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

「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とかの場合の実装ってどうしてんだろ