AllIsHackedOff

Just a memo, just a progress

Firebase Authから返ってくるJWTをゴニョゴニョする(1)

同じモチベーションの人がいたら幸いです。

モチベーション

  • Rails にしろGoにしろ自前でAuth周りを書くのがだるい

    • ログイン機能は必要だけど、さっさと作って使われるか検証したい
    • 認証なしでやればいいじゃん?って話もあるけど、なんだかんだMyhogehogeみたいな機能を実装して試したい
  • Rails -> devisev? => なにか起こる度にRack::MiddleWareとかにログを指して折ってくのつらい..

    • 2010年くらい(@Masashisalvadorが学生だったころ)から同じような感じ..
  • Go

    • コードは読みに行けるけど..自前で実装すんのかよ => 本質的な機能の部分をさっさと実装したい (=認証は外付けにしたい)
  • FirebaseにAuthあるじゃん

    • 公式SDKはNodeとかか…
    • Googleがやってくれるならメンテしてくれるだろうし、無茶苦茶なことにはならないだろうからいいな
    • ID/Passwordログインも、各種サービスのOAuthに対応してる => 勝てる?
    • AuthはRest API経由だとできないのか => JWTを使えばまあ認証サーバの切り出し(=Firebaseにおまかせ)はできそうだな。
    • 3rd partyでホゲる際のことも公式ドキュメントに書いてあるし、いきなりAuthだけつかうんじゃねえ!とはならなそう。
    • ついでにPushとかもFirebase経由で送れるのは便利だ。

最終的にやりたいこと

  • Firebaseから飛んでくるJWTをよしなにパースして認証したい。
    • Rails or Goで(Goは技術調査も兼ねた自前プロジェクトで使う)
    • RailsだったらGem入れる or コードコピーしてrequireしてゴニョるくらいで使いたい = 使い回しを容易にしたい

とりあえず認証をザクっと書いてJWTの中身を覗いてみる

コードはクソ雑ですが…

onClickSignInWithFacbook() {
    firebase.auth().signInWithPopup(provider).then((result) => {
      // This gives you a Facebook Access Token. You can use it to access the Facebook API.
      global.rrr = result;
      let token = result.credential.accessToken;
      // The signed-in user info.
      let user = result.user;
      // ...3
      this.setState({ user });
      console.log(token);
      console.log(user);
    }).catch((error) => {
      console.log(error);
      console.log("Error on facebook auth");
      // Handle Errors here.
      let errorCode = error.code;
      let errorMessage = error.message;
      // The email of the user's account used.
      let email = error.email;
      // The firebase.auth.AuthCredential type that was used.
      let credential = error.credential;
      // ...
    });
  }
  onClickSignInWithGoogle() {
    firebase.auth().signInWithPopup(gProvider).then((result) => {
      // This gives you a Google Access Token. You can use it to access the Google API.
      global.rrr = result;
      let token = result.credential.accessToken;
      // The signed-in user info.
      let user = result.user;
      this.setState({ user });
  
      console.log(token);
      console.log(user);
      // ...
    }).catch((error) => {
      console.log("error on google auth");
      console.log(error);
      // Handle Errors here.
      let errorCode = error.code;
      let errorMessage = error.message;
      // The email of the user's account used.
      let email = error.email;
      // The firebase.auth.AuthCredential type that was used.
      let credential = error.credential;
      // ...
    });    
  }

Google Authの場合

rrr.credential.idToken # .でくぎられたJWTが入ってる
rrr.credential.idToken.split(".") # header / claims / signatureに別れてる

1つめのセグメント = splitした配列の先頭はただのヘッダのはず

echo -n "eyJhbGciOiJSUzI1NiIsImtpZCI6IjdhOWY5Yzc5ZGYzMDU5MzhlMjY4ODk3Y2JkNDc1ZDQ3MjY4MWI0ZWEifQ" | base64 -D                                                                                                                 
{"alg":"RS256","kid":"7a9f9c79df305938e268897cbd475d472681b4ea"%

JWTのヘッダが入っている。閉じカッコがないのが気になるがこれは? kidは公開鍵を探しに行く時に使う。

echo -n "2つ目のセグメント" | base64 -D | jq .
{
  "azp": "xxxxx.apps.googleusercontent.com",
  "aud": "xxxxx.apps.googleusercontent.com",
  "sub": "1111111111111111111",
  "email": "hoge@example.com",
  "email_verified": true,
  "at_hash": "xxxxxxx",
  "iss": "accounts.google.com",
  "iat": 1495084945,
  "exp": 1495088545
}

JWTのStandardぽいものに、認証に使ったemailなどが入っている。

Facebook Authの場合

上記コードの result.user.Yd というキーで JWTが入っていた。google Auth の場合も同様でした。(googleの場合はcredentialsにも入っている)

rrr.user.Yd.split(".")
{
  "iss": "https://securetoken.google.com/xxxx",
  "name": "Masashi Salvador Mitsuzawa",
  "picture": "https://scontent.xx.fbcdn.net/v/t1.0-1/p100x100/16142336_1426152514084193_2027921832216883945_n.jpg?oh=8d6238d7d50034e2fd7208c8a4208fc1&oe=59745742",
  "aud": "xxxx",
  "auth_time": 1495086800,
  "user_id": "xxxx",
  "sub": "xxxx",
  "iat": 1495086800,
  "exp": 1495090400,
  "email": "xxxxx@yahoo.co.jp",
  "email_verified": false,
  "firebase": {
    "identities": {
      "facebook.com": [
        "1562766520422791"
      ],
      "email": [
        "m_m_moonty@yahoo.co.jp"
      ]
    },
    "sign_in_provider": "facebook.com"
  }
}

こんな感じでClaimsがDecodeできる。

} (closing bracket)が消える問題

echo -n "eyJhbGciOiJSUzI1NiIsImtpZCI6IjdhOWY5Yzc5ZGYzMDU5MzhlMjY4ODk3Y2JkNDc1ZDQ3MjY4MWI0ZWEifQ" | base64 -D                                                                                                                 
{"alg":"RS256","kid":"7a9f9c79df305938e268897cbd475d472681b4ea"% # }が消えてる

同じ文字列を Golang で書いた適当なコードでdecodeしてやると

(string) (len=64) "{\"alg\":\"RS256\",\"kid\":\"e62eaba0e06fe1463746c69efa974c1152234634\"}"

問題なく閉じカッコが出現する

}エンコードすると

echo -n "}" | base64
fQ==

となるので、たしかに文字列としてはたりなさそうに見える。 Base64自体はRFCかなにかで規定されている(はず)なので、挙動差異は変なのだが…何なのだろう?