Vue.js + Firebase + golangでログインシステムを作ってみた備忘録

# はじめに
こんな感じのものをvue.js + Firebase + golangで作ってみました。
![demo2.mov.gif](https://qiita-image-store.s3.amazonaws.com/0/234760/d830389d-b7c8-7230-56a9-24cf847aa83f.gif)

golangは多少慣れていますが、vueとfirebaseは初めてのだったので備忘録を残しておきます。
特にフロントはほとんど経験もなく、vueも今回が初めてなので参考資料のソースにはすごく助けられました。

# 何を作ったのか？
ログイン（メール認証・Google認証・Twitter認証・Facebook認証）認証ができます。
発行したJWTをHeader情報に詰め込んでAPIサーバにリクエストを投げます。
APIサーバは許可されたJWTかの確認を行い、問題なければレスポンスを返すといったものです。

個人でWEBサービスを作った時にログイン画面っているだろうから、ちょこっと触って慣れておこう
と思い学んでみました。なので作りは雑です。

**ソースコードはgithubに載せています。**

- https://github.com/yoshinorihisakawa/test-vue
    - firebaseの設定を済ませてからnpm run devで実行
- https://github.com/yoshinorihisakawa/test-server
    - go run main.goで実行

# JWT
JSON Web Tokenの略で、電子署名が出来るURL-safeなトークンです。
めっちゃ大雑把に言うと**「改竄できない鍵」**みたいなもの。

今回のシステムでどう使われているかというと、
クライアント側でJWTを発行し、サーバ側で改竄チェックをしています。

```
{Base64エンコードされたヘッダ}.{Base64エンコードされた JSON の中身}.{電子署名}
```

JWTの中身はこのようになっているので、クライアントからID情報などをJWTに詰めたりすることも出来ます。

以下、参考資料

- [JSON Web Token の効用](https://qiita.com/kaiinui/items/21ec7cc8a1130a1a103a)
- [JWT について調べた内容をまとめました。](https://qiita.com/gctoyo/items/8d0ffb265845ab8cc87c)

# クライアント
## Firebaseの設定
FirebaseコンソールのWEB用のKEYを取得して、config/dev.env.jsに記載。

```js
module.exports = merge(prodEnv, {
  NODE_ENV: '"xxx"',
  YOUR_KEY: '"xxx"',
  YOUR_DOMAIN: '"xxx"',
  YOUR_ID: '"xxx"',
  YOUR_BUCKET_ID: '"xxx"',
  YOUR_SENDER_ID: '"xxx"',
})
```

main.jsで設定値を読み込みFirebaseを使えるようにする。

```js
const config = {
  apiKey: process.env.YOUR_KEY,
  authDomain: process.env.YOUR_DOMAIN + '.firebaseapp.com',
  databaseURL: process.env.YOUR_DOMAIN + '.firebaseio.com',
  projectId: process.env.YOUR_ID,
  storageBucket: process.env.YOUR_BUCKET_ID + '.appspot.com',
  messagingSenderId: process.env.YOUR_SENDER_ID
}
firebase.initializeApp(config)
```

これだけでFirebaseが使えるようになるってすごい。

## 認証方法
Firebaseが提供しているプロバイダー認証は多くありますが、その中でも以下の4つを今回は採用し、
実装してみました。

### メール認証
**Sign inの時**
```signInWithEmailAndPassword```でemailとpasswordを送るだけでログインできます。
ログイン後にローカルストレージにjwtを保存し、home画面にリダイレクトしています。

```js
firebase.auth().signInWithEmailAndPassword(this.email, this.password).then(res => {
    res.user.getIdToken().then(idToken => {
        localStorage.setItem('jwt', idToken.toString())
    })
    this.$router.push('/')
}
```

**Sign upの時**
```createUserWithEmailAndPassword```でemailとpasswordを送るだけで登録されます。
以下の実装では登録後にSign inし、ローカルストレージにjwtを保存し、home画面にリダイレクトしています。

```js
firebase.auth().createUserWithEmailAndPassword(this.email, this.password).then(res => {
    console.log('Create account: ', res.user.email)
    firebase.auth().signInWithEmailAndPassword(this.email, this.password).then(r => {
        r.user.getIdToken().then(idToken => {
        localStorage.setItem('jwt', idToken.toString())
        })
        this.$router.push('/')
    }, err => {
        alert(err.message)
    })
})

```

### Google認証

```js
const provider = new firebase.auth.GoogleAuthProvider()
firebase.auth().signInWithPopup(provider).then(res => {
res.user.getIdToken().then(idToken => {
    localStorage.setItem('jwt', idToken.toString())
})
this.$router.push('/')
}
```

### Facebook認証
Facebookのdeveloper画面から各種設定や登録する必要があります。
https://developers.facebook.com/

```js
const provider = new firebase.auth.FacebookAuthProvider()
firebase.auth().signInWithPopup(provider).then(res => {
res.user.getIdToken().then(idToken => {
    localStorage.setItem('jwt', idToken.toString())
})
this.$router.push('/')
}
```

### Twitter認証
Twitterのdeveloper画面から各種設定や登録する必要があります。
https://developer.twitter.com/en.html

```js
const provider = new firebase.auth.TwitterAuthProvider()
firebase.auth().signInWithPopup(provider).then(res => {
res.user.getIdToken().then(idToken => {
    localStorage.setItem('jwt', idToken.toString())
})
this.$router.push('/')
}
```

## JWTをサーバへ送信
privateの時のみログインや登録時にローカルストレージに保存したjwtを取得しヘッダ情報に付与し、リクエストしています。

```js
apiPublic: async function () {
    let res = await axios.get('http://localhost:8000/public')
    this.msg = res.data
},
apiPrivate: async function () {
    let res = await axios.get('http://localhost:8000/private', {
    headers: {'Authorization': `Bearer ${localStorage.getItem('jwt')}`}
    })
    this.msg = res.data
}
```

## その他
Vuetifyを使ってデザインは作ってみました。ブートストラップみたいにコンポーネントが色々用意されているので簡単に使えました。

JWTをCookie、ローカルストレージのどちらに保存するか迷いましたが、
調べてもどっちでも問題ないような情報が多かったため、そこまで考えずにローカルストレージにしました。
どっちがいいいのかは自分なりの答えをどこかで考えたいです。

# サーバ
## CORS設定
**最初フロントからAPIを呼び出してもうまく動きませんでした。**
理由はCORS (Cross-Origin Resource Sharing)制約によるものでした。
**APIサーバ側で許可をしてあげる必要があります。**

```golang
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
    AllowOrigins: []string{"http://localhost:8081"},
    AllowHeaders: []string{echo.HeaderAuthorization},
    AllowMethods: []string{echo.GET},
}))
```

今回はechoを使ったためこんな感じで設定しました。

以下、参考資料

- [APIサーバを立てるためのCORS設定決定版](https://qiita.com/hirohero/items/886733f50f37404235db)

## JWTの取得&有効確認
FirebaseのSDKを使用し、JWTの改竄チェックを行いました。

```golang
// FirebaseのSDKを使用するためのkeyを読み込み
opt := option.WithCredentialsFile("/Users/yoshinori.hisakawa/keys/firebase-key.json")
app, err := fire.NewApp(context.Background(), nil, opt)
if err != nil {
    fmt.Printf("error: %v\n", err)
    os.Exit(1)
}
auth, err := app.Auth(context.Background())
if err != nil {
    fmt.Printf("error: %v\n", err)
    os.Exit(1)
}

// クライアントから送られてきた JWT 取得
authHeader := c.Request().Header.Get("Authorization")
idToken := strings.Replace(authHeader, "Bearer ", "", 1)

// JWT の検証
token, err := auth.VerifyIDToken(context.Background(), idToken)
if err != nil {
    u := fmt.Sprintf("error verifying ID token: %v\n", err)
    return c.JSON(http.StatusBadRequest, u)
}
```

# 最後に
1日かけて色々試してみました。
作りこむ必要はありますが、なんとなくログイン機能を実装する場合のイメージが湧きました。
フロント書けるようになればもっと出来ること広がるな-と思ったのでvueを中心に勉強しようと思います。

# 参考記事

- [「Vue.js + Go言語 + Firebase 」で始める! Frontend & Backend API 両方で認証するセキュアなSPA開発ハンズオン!](https://qiita.com/po3rin/items/d3e016d01162e9d9de80)

- [Vue 2 + Firebase: How to build a Vue app with Firebase authentication system in 15 minutes](https://medium.com/@anas.mammeri/vue-2-firebase-how-to-build-a-vue-app-with-firebase-authentication-system-in-15-minutes-fdce6f289c3c)
- [Vue.js + Firebase を使って爆速でユーザ認証を実装する](https://qiita.com/sin_tanaka/items/ea149a33bd9e4b388241)
- [遂にFirebase Admin SDK Goが登場！](https://qiita.com/koki_cheese/items/2d111b2b074bfa697776)