【Golang】echoを使ってjwtによる認証をしてみた

## はじめに
JWTによる認証について調べてみた時に軽く動くものを書いてみたときのもの

## JWT(JSON Web Tokens)
クライアント・サーバー間で認証する手段の一つ。
ちなみにJWTと書いてジョットと読むらしい。


### フォーマット

```
{base64エンコードしたheader}.{base64エンコードしたclaims}.{署名}
```

### メリット

- 安全
  - JWTに署名が含まれているため、改ざんがあってもチェックできるようになっている
- 管理のしやすさ
  - URLに含むことができる文字で構成されているからhttpリクエストでの取り扱いがしやすい

などがある。


JWTに関するこれ以上の詳しい説明は今回は省く。

より詳しく知りたければ
https://jwt.io/
あたりを参考にすると良さそう。

## 今回実装するもの
今回はログインと、ログイン済みの時にレスポンスを取得する機能のみとする

- `POST /login`: ログイン処理
- `GET /restricted/welcome`: 取得したトークンを付与してリクエストした時のみレスポンスが得られる

## 実装
ディレクトリ構成

```
.
├── handler
│   └── handler.go
└── server.go
```

リクエストに対する処理を下記のように実装する

```handler/handler.go
package handler

import (
	"net/http"
	"time"
	"github.com/labstack/echo"
    "github.com/dgrijalva/jwt-go"
)

func Login() echo.HandlerFunc {
	return func(c echo.Context) error {
		username := c.FormValue("username")
		password := c.FormValue("password")

		if username == "test" && password == "test" {
			token := jwt.New(jwt.SigningMethodHS256)

			// set claims
			claims := token.Claims.(jwt.MapClaims)
			claims["name"] = "test"
			claims["admin"] = true
			claims["iat"] = time.Now().Unix()
			claims["exp"] = time.Now().Add(time.Hour * 72).Unix()

			// generate encoded token and send it as response
			t, err := token.SignedString([]byte("secret"))
			if err != nil {
				return err
			}
			return c.JSON(http.StatusOK, map[string]string{
				"token": t,
			})
		}

		return echo.ErrUnauthorized
	}
}

func Restricted() echo.HandlerFunc {
	return func(c echo.Context) error {
		user := c.Get("user").(*jwt.Token)
		claims := user.Claims.(jwt.MapClaims)
		name := claims["name"].(string)

		return c.String(http.StatusOK, "Welcome " + name + "!")
	}
}
```

ルーティングなどは下記のように行う

```server.go
package main

import (
	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"

	"jwt_sample/handler"
)

func main() {
	e := echo.New()

	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	e.POST("/login", handler.Login())
	r := e.Group("/restricted")
	r.Use(middleware.JWT([]byte("secret")))
	r.GET("/welcome", handler.Restricted())

	e.Logger.Fatal(e.Start(":1323"))
}
```

## 実行

まずは起動させてみる

```
$ go run server.go

   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.1.16
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:1323
```

正常に起動できてることを確認したら`/login`でtokenを取得する。

```
$ curl -X POST -d 'username=test' -d 'password=test' localhost:1323/login
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNTk1OTI5MDcxLCJuYW1lIjoidGVzdCJ9.PF-0TU9aYx6CeLEN8q-EHEARkVrFQxBXYsOu173Sdno"}
```

取得したトークンをヘッダーのAuthorizationにsetし、`/restricted`にリクエストするとWelcomeと表示される。

```
$ curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNTk1OTI5MDcxLCJuYW1lIjoidGVzdCJ9.PF-0TU9aYx6CeLEN8q-EHEARkVrFQxBXYsOu173Sdno" localhost:1323/restricted/welcome
Welcome test!
```

また、試しに誤ったトークンでリクエストしてみると

```
$ curl -H "Authorization: Bearer eyJhbGciOiJIU" localhost:1323/restricted/welcome
{"message":"invalid or expired jwt"}
```
としっかり弾かれることが確認できる。


## 参考
- [Go で JWT を使うユースケースとその実装例](https://qiita.com/yoheimuta/items/b17bfbac17c02d410f54)
- [Go言語で理解するJWT認証 実装ハンズオン](https://qiita.com/po3rin/items/740445d21487dfcb5d9f)
- [Golang(ECHO+GORM)でJWTとGraphQLの環境を構築](https://qiita.com/hiroykam/items/31862832a562388d876b)
- [Go言語でEchoを用いて認証付きWebアプリの作成](https://qiita.com/x-color/items/24ff2491751f55e866cf)