なにこれ
筆者がフロントエンド(Nuxt.js)からGoで作ったAPIサーバーにGETリクエストを送りたい!
って時に、Go側でfirebaseの認証をしようとしました。
その実装でかなり詰まったので、解決方法を備忘録として残しておきます。
やりたいこと
- フロントエンドからバックエンドへGETリクエストを送る
- その時に、headerのAuthorizationにfirebaseのidTokenを乗せる
- バックエンドは送られたリクエストからheaderのidTokenを見て、firebase認証する
- okならstatus200とレスポンスを返す
- 認証に失敗したら401を返す
注意事項
Goを独学して1ヶ月で、もしかしたら記事の内容や自分の認識に間違いがあるかもしれません。
その時は小さな点でも構わないので、ご指摘いただけると非常に助かります!
前提条件
Goのwebフレームワークにginを使用しています
認証にはfirebase authを仕様
この記事で説明しないこと
firebase authの説明
ginの説明
axiosの説明
問題にぶち当たった話
axiosの設定
フロントエンド(http://localhost:3000/
)からaxiosを使って、
http://localhost:8080/users
へGETリクエストを送ります
リクエストを送る前にaxiosをラップして、storeからidTokenをgetして、headersに乗せます
export default (ctx) => {
ctx.$axios.onRequest((config) => {
config.headers.Authorization = `Bearer ${ctx.store.getters['getIdToken']}`
return config
})
}
Go側のルーティング
内容を要約すると、routerを初期化して、
midllewareとしてcorsの許可と、
firebase SDKで認証しています。
認証ならc.Next()
で次へ、認証失敗したら401エラーを返すようにしています
最後にUsersのエンドポイントを定義
func main() {
router := gin.Default()
router.Use(Cors())
router.Use(Auth())
router.GET("/users", userController.Index)
}
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
c.Writer.Header().Set("Access-Control-Max-Age", "86400")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, token, accept, origin, Cache-Control, X-Requested-With")
c.Writer.Header().Set("Access-Control-Allow-Methods", "OPTIONS, POST, PATCH, GET, PUT, DELETE")
c.Next()
}
}
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
firebaseApp := NewFirebaseApp()
authClient := NewAuthClient(firebaseApp)
authorization := c.Request.Header.Get("Authorization")
idToken := strings.Replace(authorization, "Bearer ", "", 1)
ctx := c.Request.Context()
token, err := authClient.VerifyIDToken(ctx, idToken)
if err != nil {
ctx.AbortWithStatusJSON(statusCode, gin.H{
"errorMessage": string(err.Error()),
})
return
}
c.Next()
}
}
実際にフロントエンドからリクエストを送る
この状態でaxiosを使って、↓のコードでリクエストを送りました
<script>
export default {
async asyncData(ctx) {
try {
const response = await ctx.$axios.get("http://localhost:8080/users")
const users = response.data
return {
users,
}
} catch(err) {
console.error(err)
}
},
}
</script>
結果
見るとフラストレーションがたまる、CORSエラーが吐かれました
Access to XMLHttpRequest at 'http://localhost:8080/users' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
エラー後半の
doesn't pass access control check: It does not have HTTP ok status.
という文字が気になってginのログを確認しました。
そしたら、
[GIN] 2021/05/19 - 23:43:36 | 401 | 6.8268ms | 172.20.0.1 | OPTIONS "/users/
となっていました。
問題点
GETメソッドでリクエストで送ったはずなのに、なぜかOPTIONSメソッドになってる。。。
CORSの設定でOPTIONSメソッドを許可していたので、途方に暮れました。泣
解決策
関数Corsに↓を追加しました。
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:3009")
c.Writer.Header().Set("Access-Control-Max-Age", "86400")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, token, accept, origin, Cache-Control, X-Requested-With")
c.Writer.Header().Set("Access-Control-Allow-Methods", "OPTIONS, POST, PATCH, GET, PUT, DELETE")
// 追加
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}
理由
プリフライトリクエストの時は、204を返して処理を終了させたいためです!
プリフライトリクエストについて調べたので、そちらも説明します。
(個人が噛み砕いただけなので間違ってる可能性があります。気になる方は調べることをオススメします)
プリフライトリクエストってなんやねん
プリフライトリクエストとは
google翻訳から参照
プレフライト(preflight check)は「pre(前)+flight(飛ぶ)」で飛び立つ前のチェック(check)の意味で、DTPでは印刷や刷版を出力する前のチェックのことを指します。 プリフライト・フライトチェックとも表現します。
ざっくり言うと、
今回のようにheaderに任意のデータを追加する時は、
GETリクエストの前にバックエンドへリクエストが一度送られます。
(この時のリクエストのメソッドがOPTIONSになる)
バックエンドがプリフライトリクエストを見てokなら、
再度GETリクエストを送ります。
プリフライトリクエストの解説は↓の記事が非常に分かりやすいと感じました!
CORSの仕組みをGIFアニメで分かりやすく解説
結果
無事にフロントエンドでレスポンスを受け取ることができました!
参考
今回抜粋したソースコードの全体は↓になります
https://github.com/finder-app/finder-backend
解決策を知ったstack overflow
https://stackoverflow.com/questions/29418478/go-gin-framework-cors
CORSとはなんぞや、プリフライトリクエストを説明していただいた素晴らしい記事
https://coliss.com/articles/build-websites/operation/work/cs-visualized-cors.html