Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

こんな感じのものをvue.js + Firebase + golangで作ってみました。
demo2.mov.gif

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

何を作ったのか?

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

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

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

JWT

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

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

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

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

以下、参考資料

クライアント

Firebaseの設定

FirebaseコンソールのWEB用のKEYを取得して、config/dev.env.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を使えるようにする。

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画面にリダイレクトしています。

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画面にリダイレクトしています。

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認証

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/

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

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を取得しヘッダ情報に付与し、リクエストしています。

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サーバ側で許可をしてあげる必要があります。

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

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

以下、参考資料

JWTの取得&有効確認

FirebaseのSDKを使用し、JWTの改竄チェックを行いました。

// 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を中心に勉強しようと思います。

参考記事

yoshinori_hisakawa
Go言語/クリーンアーキテクチャ/DDD/Docker/OOP/Angular/Java/設計/ Fringe81に興味ある方は、メールください! yoshinori_hisakawa@fringe81.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした