Golang によるfirebase AuthenticationにおけるJWT解析

# 概要

Firebase Authenticationの機能を使用した際、フロントエンドでJWTを取得できる。このJWTは、改ざんがされていないことを検知可能なので、バックエンドでは、このJWTを用いてユーザ情報を取得できる。

今回紹介するライブラリは、サーバーに送られてきた、以下の形式のJWTを分解し、そして、デコードすることでFirebaseにおけるユーザー情報を取得する処理をGolangで実装した。

```bash
{base64エンコードしたhead１er}.{base64エンコードしたclaims}.{署名}
```

発行者(今回は、Firebase)が鍵を使用して、JSONに署名することでトークンとして扱うことができる。また、鍵を使用して検証することで改善を検知できる。
claims部には、ユーザー名などの任意の情報を含めることができる。

git: [k-washi/jwt-decode](https://github.com/k-washi/jwt-decode)

# インストール

```bash
go get -u github.com/k-washi/jwt-decode/jwtdecode
``` 

# 使用例

以下のプログラムを次のようなコマンドで実行した結果、firebaseに格納されたユーザIDとメールアドレスが取得できる。

```bash
> go run main.go
User ID: qZhsF2HfuWZEBghFa4nl2Kidyp22 ,Email: test@test.com
```

```golang:main.go

package main

import (
	"fmt"
	"log"

	"github.com/k-washi/jwt-decode/jwtdecode"
)

func main() {
	jwt := "eyJhbGciOiJSUzI1NiIsImtpZCI6ImEw ...." //自分で取得したJWTを記載する

	//JWTをheader, claims, 署名に分解する。
	hCS, err := jwtdecode.JwtDecode.DecomposeFB(jwt)
	if err != nil {
		log.Fatalln("Error : ", err)
	}

	//hCS[1] ユーザー情報が入ったclaims部分をユーザー情報に変換する。
	/*
		//FireBaseCustomToken auth.token add email
		type FireBaseCustomToken struct {
			auth.Token
			Email string `json:"email"`
		}
		の構造にJWTを変換する。
		auth.TokenにEmailを入れて拡張した構造体。
	*/
	payload, err := jwtdecode.JwtDecode.DecodeClaimFB(hCS[1])
	if err != nil {
		log.Fatalln("Error :", err)
	}

	//ユーザーIDと, メールアドレスを表示
	user := payload.Subject
	email := payload.Email
	fmt.Printf("User ID: " + user + " ,Email: " + email + "\n")

```

# 解説

パッケージの型をjwtDecodeInterfaceで定義し、JwtDecodeとして公開。そのため、上記のようにjwtdecode.JwtDecodeでアクセスできる。
関数を使用する時、公開しているJwtDecodeに構造体jwtDecodeを格納することで、jwtDecodeに埋め込んだ関数を使用できる。

```golang:jwtDecode.go

package jwtdecode

type jwtDecodeInterface interface {
	DecomposeFB(string) ([]string, error)
	DecodeClaimFB(string) (*FireBaseCustomToken, error)
}

var (
	//JwtDecode jwt decode and parse user info in firebase
	JwtDecode jwtDecodeInterface
)

type jwtDecode struct{}

func init() {
	JwtDecode = &jwtDecode{}
}
```

以下は、jwtDecodeの構造体に埋め込んだ関数である。JWTを分解するDecomposeFBと、claims部分をデコードするDecodeClaimFBを実装している。デコードの結果が、FireBaseCustomToken型で抽出される。

```golang:jwtFB.go

package jwtdecode

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"firebase.google.com/go/auth"
)

//FireBaseCustomToken auth.tokenをemailで拡張
/*
	type Token struct{
		Issuer string "json:\"iss\"";
		Audience string "json:\"aud\"";
		Expires int64 "json:\"exp\"";
		IssuedAt int64 "json:\"iat\"";
		Subject string "json:\"sub,omitempty\"";
		UID string "json:\"uid,omitempty\"";
		Claims map[string]interface{}
		"json:\"-\""
	}
*/
type FireBaseCustomToken struct {
	auth.Token
	Email string `json:"email"`
}

//DecomposeFB  JWTをHeader, claims, 署名に分解
func (s *jwtDecode) DecomposeFB(jwt string) ([]string, error) {
	hCS := strings.Split(jwt, ".")
	if len(hCS) == 3 {
		return hCS, nil
	}
	return nil, errors.New("Error jwt str decompose: inccorrect number of segments")

}

//DecodeClaimFB JWTのclaims部分をFireBaseCustomTokenの構造体にデコード
func (s *jwtDecode) DecodeClaimFB(payload string) (*FireBaseCustomToken, error) {
	payloadByte, err := base64.RawURLEncoding.DecodeString(payload)
	if err != nil {
		return nil, errors.New("Error jwt token decode: " + err.Error())
	}

	var tokenJSON FireBaseCustomToken
	err = json.Unmarshal(payloadByte, &tokenJSON)
	if err != nil {
		fmt.Println("Error: ", err)
		return nil, errors.New("Error jwt token unmarshal: " + err.Error())
	}

	return &tokenJSON, nil
}
```