4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

フロントエンド(Nuxt)からheaderにidTokenを乗せて、バックエンド(Go)で認証してレスポンスを返す方法

Posted at

なにこれ

筆者がフロントエンド(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に乗せます

axios.js
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のエンドポイントを定義

router.go
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を使って、↓のコードでリクエストを送りました

index.vue
<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に↓を追加しました。

router.go
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アニメで分かりやすく解説

結果

無事にフロントエンドでレスポンスを受け取ることができました!
finder-frontend_-_frontend.png

参考

今回抜粋したソースコードの全体は↓になります
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

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?