Closed9
GoのGraphqlサーバーにAuth0を導入する
ログイン機構などがない状態のGraphqlのサーバーにAuth0を導入する作業中のメモ
frontはnext.jsなので一旦そっちでのログイン処理を実装
ログインしてaccessToken, idTokenは取れたので次はこのあたり参照している(そもそも)
実装的にはこれを一番参考にした
audが合わないって怒られたけど、フロント側でログインするときにaudience指定してなかったのでAUTH0_AUDIENCE
入れたら普通に行けた
nextjsなのにSPAを見てたからややこしかったのかもしれない
REGULAR APPでフレームワークをNext.jsを選べばちゃんとインストラクション出てきた(こっちではaudienceの指定がある)
とりあえずGraphqlをハンドルしているエンドポイントにmiddleware仕込んだけど、次のことがまだ残っている
- 特定のquery/mutationだけ認証する(現状、ユーザー作成時など、認証しないquery/mutationが素通りできない)
- 認証した該当ユーザーの情報をコンテクストに含める(?)
そもそもどこで認証するか問題を考えないといけないか
これよさそう
結局、graphqlに入る前に
- Authorizationヘッダーを見て、アクセストークンの認証
- subをcontextに入れる
までやるようにした
func newAuth0Middleware() *jwtmiddleware.JWTMiddleware {
issuerURL, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/")
if err != nil {
log.Fatalf("Failed to parse the issuer url: %v", err)
}
provider := jwks.NewCachingProvider(issuerURL, 5*time.Minute)
jwtValidator, err := validator.New(
provider.KeyFunc,
validator.RS256,
issuerURL.String(),
[]string{os.Getenv("AUTH0_AUDIENCE")},
validator.WithCustomClaims(
func() validator.CustomClaims {
return &CustomClaims{}
},
),
validator.WithAllowedClockSkew(time.Minute),
)
if err != nil {
log.Fatalf("Failed to create JWT validator: %v", err)
}
return jwtmiddleware.New(
jwtValidator.ValidateToken,
jwtmiddleware.WithCredentialsOptional(true),
)
}
あくまでトークンがあればそれが有効であるかをチェックするにとどめている
認可はGraphqlの中でやる想定なので、トークンがない場合は素通りさせる
そのために以下のオプションを足している
jwtmiddleware.WithCredentialsOptional(true),
subをcontextに詰めるのはこれ
func newAuthExtractor() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims, ok := r.Context().Value(jwtmiddleware.ContextKey{}).(*validator.ValidatedClaims)
if !ok {
log.Println("invalid claims")
next.ServeHTTP(w, r)
return
}
sub := claims.RegisteredClaims.Subject
ctx := context.WithValue(r.Context(), AUTH0_SUBJECT_KEY, sub)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
ここも、そもそもトークンがない場合もあるのでエラーにはしないで次に流している
あとはGraphqlでDirectiveを設定
directive @isAuthenticated on FIELD_DEFINITION
必要なQueryにつける
extend type Query {
user(id: ID!): User @isAuthenticated
currentUser: User @isAuthenticated
}
そして必要なロジックをDirectiveに実装すればよかった
func NewDirectiveRoot(services *service.Services) graph.DirectiveRoot {
isAuthenticated := NewIsAuthenticated(services.User)
return graph.DirectiveRoot{
IsAuthenticated: isAuthenticated,
}
}
func NewIsAuthenticated(userService service.UserService) func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {
return func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {
sub := auth0.GetAuth0Subject(ctx)
_, err = userService.GetUserBySub(ctx, sub)
if err != nil {
log.Printf("err: %v", err)
return nil, errors.New("unauthorized")
}
return next(ctx)
}
}
srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: resolver.NewResolver(services), Directives: directive.NewDirectiveRoot(services)}))
このスクラップは2023/10/20にクローズされました