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.TokenObjectの 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への変換が所見だったのでこのあたりの知識を補強しておく