🐙
Golangでchiを使ってAPIキーの認証を自作する
モチベーション
Goを使ったAPIを作成しており、APIキーの認証が必要となりました。AzureのAPI ManagementやAWSのAPI Gateway、OSSのKong Gatewayなどを利用して認証部分の実装をおまかせしても良いのですが、(お金や運用コストを鑑みて)自作してみることにしました。
Goにはecho, ginなどの軽量で素晴らしいwebフレームワークがありますが独自のcontextを使っているため多少慣れが必要です。今回はchiは標準のcontextに準拠している点を評価して採用しました。
https://github.com/go-chi/jwtauth を使っても良いのですが、クッキーからトークンを拾ってくる機能がついていたりするので最小限の機能だけで実装するために自作しました。https://github.com/lestrrat-go/jwx を信用しているので認証まわりで他のライブラリを噛ませたくないという気持ちもあります。
コード
package main
import (
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwt"
)
var key jwt.SignEncryptParseOption
func GenToken(id string) {
t := jwt.New()
t.Set("id", id)
sampleToken, err := jwt.Sign(t, key)
if err != nil {
panic(err)
}
fmt.Println(string(sampleToken))
}
func init() {
key = jwt.WithKey(jwa.HS256, []byte("secret"))
GenToken("123") // OK
GenToken("456") // NG
}
func GetToken(r *http.Request) string {
const bearer = "Bearer"
token := r.Header.Get("Authorization")
if len(token) > 7 && token[:7] != bearer { // 7 is len("Bearer ")
return token[7:]
}
return ""
}
func MyMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := GetToken(r)
fmt.Println("get token: ", token)
jwtToken, err := jwt.Parse([]byte(token), key)
if err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusUnauthorized)
return
}
// validate token
err = jwt.Validate(jwtToken)
if err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusUnauthorized)
return
}
// check id
id, ok := jwtToken.Get("id")
if !ok {
w.WriteHeader(http.StatusUnauthorized)
return
}
if id != "123" {
w.WriteHeader(http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(MyMiddleware)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("welcome"))
})
http.ListenAndServe(":3001", r)
}
id: "123"の方のJWTを使って認証するとうまくいきました。
% curl -H"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEyMyJ9.HdEPYqE7fzGkBWBZyI-Cc8-gVQlNd2zBM7PwK47p67o" http://localhost:3001/
welcome
おわりに
APIキーをサーバー側で発行して、ユーザーはキーをそのまま認証に用いることを想定した実装ですが、かなり簡単にできました。実用的にはKVやDBと組み合わせた用途になると思うので次回はそちらをやってみようと思います。
お仕事のご連絡お待ちしています。contact@tychy.jp まで
Discussion