Firebase Authから返ってくるJWTをゴニョゴニョする (2)
前回の記事 allishackedoff.hatenablog.com の続きです。引き続きゴニョゴニョします。
JWTをDecodeする
golangのJWTのライブラリは github.com が最も使われているようです。 otiai10.hatenablog.com christina04.hatenablog.com この辺りでも利用されているようで、今回トライする際に参考にさせて頂きました。
Firebase Authから返ってくるJWTを検証する
検証するにはヘッダーをdecodeしないといけません。
headerに含まれる kid
に対応するCERTIFICATEを使ってtokenが正しいかどうかを検証します。
「文字列としてcertificateを渡せばよしなにやってくれるやろ」とか甘いことを考えていましたが、しっかりrsa.PublicKey
型のkeyを抜き出してやらないと
verifyできません。
検証の流れは下記です。
- JWTのヘッダーをDecode
- "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com からcertificateのリストを取得
- 取得したcertificateのリストから、 ヘッダーの
kid
に対応するkeyを見つける- 見つからない場合はexpireしている
- 見つけたkeyを
x509
パッケージを利用してrsa.PublicKey
型の変数にする - Tokenをdecode
- decodeで生成された
jwt.Token
Objectの Validを見る- expireしている / publickeyがうまく取得できていない場合は
token.Valid == false
になる
- expireしている / publickeyがうまく取得できていない場合は
ソースコード (in Golang)
package main import ( "encoding/json" "encoding/pem" "errors" "fmt" "log" "net/http" "strings" "crypto/x509" "crypto/rsa" jwt "github.com/dgrijalva/jwt-go" ) type header struct { Alg string `json:"alg"` Kid string `json:"kid"` } type payload struct { jwt.StandardClaims } func main() { const ( publicKeySrcURL = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com" ) rawToken := "<PASTE YOUR Firebase Auth JWT" segs := strings.Split(rawToken, ".") bHeader, err := jwt.DecodeSegment(segs[0]) if err != nil { log.Fatalln("error in decoding segment header") } var h header json.Unmarshal(bHeader, &h) resp, err := http.Get(publicKeySrcURL) if err != nil { log.Fatalln("error in getting public key") } defer resp.Body.Close() // 一応Streamで扱っておく decoder := json.NewDecoder(resp.Body) pubkeyMap := make(map[string]string) err = decoder.Decode(&pubkeyMap) if err != nil { fmt.Printf("%v", err) log.Fatalln("error on decoding http response body") } rawPubkey, ok := pubkeyMap[h.Kid] if !ok { log.Fatalln("public key corresponding to XXX is not found") } pk, err := parseKey([]byte(rawPubkey)) if err != nil { panic(err) } token, err := jwt.Parse(rawToken, func(token *jwt.Token) (interface{}, error) { if _, ok = token.Method.(*jwt.SigningMethodRSA); !ok { return nil, errors.New("signing methods other than RSA are not expected") } if token.Method != jwt.SigningMethodRS256 { return nil, errors.New("Signing methods other than RS256 are not expected") } return pk, nil }) //spew.Dump(token) if !token.Valid { fmt.Println("token is invalid") } } // --CETIFICATE -> rsa.PublicKeyへの変換 func parseKey(rawPubkey []byte) (interface{}, error) { block, _ := pem.Decode(rawPubkey) cert, _ := x509.ParseCertificate(block.Bytes) pk := cert.PublicKey.(*rsa.PublicKey) fmt.Println(pk) return pk, nil }
string -> []byteへのキャストをやっているのがイケてないですが、とりあえずこれでdecodeできます。
NOTE
Certificate -> rsa.PublicKeyへの変換が所見だったのでこのあたりの知識を補強しておく