はじめに
こんな感じのものをvue.js + Firebase + golangで作ってみました。
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に詰めたりすることも出来ます。
以下、参考資料
クライアント
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を中心に勉強しようと思います。