Golang GinでAuth0のJWTを検証する
公開日:
更新日:
こんにちは。株式会社フルーデンスの小巻です。
前回の記事にも書いたので重複しますが、現在、Angularで簡易的なWebアプリを作っており、Auth0を使っています。
前回は、Cloudflare Workersを利用する方針で進めておりました。しかし、もろもろ検討した結果、Golang GinでAPIサーバーを起動することにしました。
そのため、タイトルの通りですが、Golang Ginのミドルウェアで、Auth0のJWTを検証したので、その際の記録です。
私は、最近Golangを勉強し始めた程度ですので、間違っていたり、もっと良い書き方があれば、コメント頂ければと思います。
Auth0やAngularの準備
前回の記事に記載しておりますので、省略します。
Cloudflare WorkersでAuth0のJWTを検証する | 株式会社フルーデンス
Golang Ginの準備
auth0/go-jwt-middleware: A Middleware for Go Programming Language to check for JWTs on HTTP requests
package main
import (
"context"
"log"
"net/http"
"net/url"
"os"
"time"
jwtmiddleware "github.com/auth0/go-jwt-middleware/v2"
"github.com/auth0/go-jwt-middleware/v2/jwks"
"github.com/auth0/go-jwt-middleware/v2/validator"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
adapter "github.com/gwatts/gin-adapter"
)
// 環境変数から取得、なければデフォルト値
var (
Auth0Domain = os.Getenv("AUTH0_DOMAIN")
Auth0Audience = os.Getenv("AUTH0_AUDIENCE")
)
type CustomClaims struct {
Scope string `json:"scope"`
Permissions []string `json:"permissions"`
}
// Validate はCustomClaimsインターフェースの実装に必要です
// 構造体のコピーを避けるため、ポインタレシーバにします
func (c *CustomClaims) Validate(ctx context.Context) error {
return nil
}
// SetupAuth0Middleware はGin用のハンドラーを返します
func SetupAuth0Middleware() gin.HandlerFunc {
issuerURL, err := url.Parse("https://" + Auth0Domain + "/")
if err != nil {
log.Fatalf("failed to parse issuer URL: %v", err)
}
provider := jwks.NewCachingProvider(issuerURL, 5*time.Minute)
jwtValidator, err := validator.New(
provider.KeyFunc,
validator.RS256,
issuerURL.String(),
[]string{Auth0Audience},
validator.WithCustomClaims(
func() validator.CustomClaims {
return &CustomClaims{}
},
),
validator.WithAllowedClockSkew(time.Minute),
)
if err != nil {
log.Fatalf("failed to initialize JWT validator: %v", err)
}
errorHandler := func(w http.ResponseWriter, r *http.Request, err error) {
log.Printf("Encountered error while validating JWT: %v", err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"error":"JWT validation failed"}`))
}
middleware := jwtmiddleware.New(
jwtValidator.ValidateToken,
jwtmiddleware.WithErrorHandler(errorHandler),
)
// gwatts/gin-adapter を使うと標準のhttpミドルウェアをGin用に1行で変換できます
return adapter.Wrap(middleware.CheckJWT)
}
func main() {
// 動作確認用フォールバック(本来は環境変数で設定)
if Auth0Domain == "" {
Auth0Domain = "tenant-pq9.jp.auth0.com"
}
if Auth0Audience == "" {
Auth0Audience = "https://api-endpoint-u5x/"
}
r := gin.Default()
// CORS設定
r.Use(cors.New(cors.Config{
// 許可するオリジン (AngularのURL)
AllowOrigins: []string{
"http://localhost:4200",
},
// 許可するメソッド
AllowMethods: []string{
"GET", "POST", "PUT", "DELETE", "OPTIONS",
},
// 許可するヘッダー
// Auth0を利用する場合、"Authorization" が必須です
AllowHeaders: []string{
"Authorization",
"Content-Type",
},
// クッキーなどを送る場合に必要
AllowCredentials: true,
// プリフライトリクエストの結果をキャッシュする時間
MaxAge: 12 * time.Hour,
}))
// Auth0 ミドルウェアを設定
r.Use(SetupAuth0Middleware())
// 独自のクレーム抽出用ミドルウェア
// context.Context (標準) から gin.Context へ値を移し替える
r.Use(func(c *gin.Context) {
token := c.Request.Context().Value(jwtmiddleware.ContextKey{})
if token != nil {
validatedClaims := token.(*validator.ValidatedClaims)
customClaims := validatedClaims.CustomClaims.(*CustomClaims)
// Gin Contextに保存して後続のハンドラーで使いやすくする
c.Set("permissions", customClaims.Permissions)
c.Set("registeredClaims", validatedClaims.RegisteredClaims)
}
c.Next()
})
r.GET("/verify", func(c *gin.Context) {
// c.MustGetを使うと存在しない場合にpanicするので、安全にGetを使う
claims, exists := c.Get("registeredClaims")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "claims not found"})
return
}
registeredClaims := claims.(validator.RegisteredClaims)
c.JSON(http.StatusOK, gin.H{
"subject": registeredClaims.Subject,
})
})
if err := r.Run(":8080"); err != nil {
log.Fatalf("failed to run server: %v", err)
}
}
サーバーを起動します。
トークンをセットしないで、リクエストします。
curl -X GET "http://localhost:8080/verify" | jq -r
teruhiro:~/go/src/github.com/terukomaki/go-gin-auth0-hf3 $ curl -X GET "http://localhost:8080/verify" | jq -r
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 33 100 33 0 0 48672 0 --:--:-- --:--:-- --:--:-- 33000
{
"error": "JWT validation failed"
}
トークンをセットして、リクエストします。
TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InBZcWxja0pWaUI5NHlLU1oySzBVVyJ9.eyJpc3MiOiJodHRwczovL3RlbmFudC1wcTkuanAuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDY3NGZiZmU3OTcwMjE1ZGE3MzQ3NzMwOCIsImF1ZCI6WyJodHRwczovL2FwaS1lbmRwb2ludC11NXgvIiwiaHR0cHM6Ly90ZW5hbnQtcHE5LmpwLmF1dGgwLmNvbS91c2VyaW5mbyJdLCJpYXQiOjE3MzQ0NDY4NjEsImV4cCI6MTczNDUzMzI2MSwic2NvcGUiOiJvcGVuaWQiLCJhenAiOiJydlp6aTB1NFQyRXg3SVhlR2pId2xZWjVadDlNUzk2USIsInBlcm1pc3Npb25zIjpbXX0.sinBFBHl2CFnyph-0l0Z_iSCqejcfAoA4iVOxvyH6mcyUvcOgFlhB52GL9kn_aw6RJWv8u_Vm_DJsdxfq1wcQL8OV4YgGkMZjfw0Y2LB4otRSVBjHYXf0UL1mi8cnWLn9oXM5nckT3KgjsJGgOx-p2p4uUcn55qjj_lqRYpmGbb3bToB2JqLA3-unXLP4A_o57bzqHJXNDxNPScuBzLt1azizaFAHcGWsoj_y98NeLqYRGx6sDqgdllStzBHREKVar2FUq5Rh5CvaH2LltneI2jOqH6w3kpXqKUUfyYPwsCc8Rw_CoCOEMTiigyxFTYahUJQfkgv133V3F_eD7LCag
curl -X GET -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/verify" | jq -r
teruhiro:~/go/src/github.com/terukomaki/go-gin-auth0-hf3 $ TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InBZcWxja0pWaUI5NHlLU1oySzBVVyJ9.eyJpc3MiOiJodHRwczovL3RlbmFudC1wcTkuanAuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDY3NGZiZmU3OTcwMjE1ZGE3MzQ3NzMwOCIsImF1ZCI6WyJodHRwczovL2FwaS1lbmRwb2ludC11NXgvIiwiaHR0cHM6Ly90ZW5hbnQtcHE5LmpwLmF1dGgwLmNvbS91c2VyaW5mbyJdLCJpYXQiOjE3MzQ0NDY4NjEsImV4cCI6MTczNDUzMzI2MSwic2NvcGUiOiJvcGVuaWQiLCJhenAiOiJydlp6aTB1NFQyRXg3SVhlR2pId2xZWjVadDlNUzk2USIsInBlcm1pc3Npb25zIjpbXX0.sinBFBHl2CFnyph-0l0Z_iSCqejcfAoA4iVOxvyH6mcyUvcOgFlhB52GL9kn_aw6RJWv8u_Vm_DJsdxfq1wcQL8OV4YgGkMZjfw0Y2LB4otRSVBjHYXf0UL1mi8cnWLn9oXM5nckT3KgjsJGgOx-p2p4uUcn55qjj_lqRYpmGbb3bToB2JqLA3-unXLP4A_o57bzqHJXNDxNPScuBzLt1azizaFAHcGWsoj_y98NeLqYRGx6sDqgdllStzBHREKVar2FUq5Rh5CvaH2LltneI2jOqH6w3kpXqKUUfyYPwsCc8Rw_CoCOEMTiigyxFTYahUJQfkgv133V3F_eD7LCag
teruhiro:~/go/src/github.com/terukomaki/go-gin-auth0-hf3 $ curl -X GET -H "Authorization: Bearer $TOKEN" \
> "http://localhost:8080/verify" | jq -r
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 44 100 44 0 0 131 0 --:--:-- --:--:-- --:--:-- 131
{
"subject": "auth0|674fbfe7970215da73477308"
}
無事、JWTが検証でき、デコードしてsubjectが取得できました。

