如何使用 Firebase 身份驗證和數據庫構建 Vue 調查應用程序
已發表: 2022-03-10
快速總結↬本教程將逐步指導您使用 Vue.js 和 Firebase 構建功能性調查應用程序。 從通過 Vuelidate 驗證用戶數據,到身份驗證、存儲用戶數據、路由保護以及將數據發送到 Firebase 服務器。 本教程中使用的所有步驟都是實用的,並且可以在任何實際項目中重現,即使使用自定義後端也是如此。
在本教程中,您將構建一個調查應用程序,我們將在其中學習驗證用戶表單數據,在 Vue 中實現身份驗證,以及能夠使用 Vue 和 Firebase(一個 BaaS 平台)接收調查數據。
在我們構建這個應用程序時,我們將學習如何處理不同類型數據的表單驗證,包括聯繫後端以檢查電子郵件是否已被接收,甚至在用戶在註冊期間提交表單之前。
此外,該應用程序將使用 RESTful API 處理用戶的登錄。 它將利用 Vue router 中的 Authguard 來阻止未登錄的用戶訪問調查表,並將已登錄用戶的調查數據成功發送到安全數據庫。
就這樣我們在同一頁面上,讓我們澄清一下 Firebase 是什麼,以及它將在本教程中做什麼。 Firebase 是一個“構建、改進和發展您的應用程序”的工具集,它使您可以訪問開發人員通常必須自己構建但實際上並不想構建的大部分服務,因為他們更願意專注於應用體驗本身。 這包括分析、身份驗證、數據庫、文件存儲等內容,不勝枚舉。
這與傳統的應用程序開發不同,後者通常涉及編寫前端和後端軟件。 前端代碼只是調用後端公開的 API 端點,而後端代碼實際完成工作。 然而,對於 Firebase 產品,傳統的後端被繞過了,將工作交給了客戶端。 這在技術上允許像我這樣的前端工程師只編寫前端代碼來構建全棧應用程序。
跳躍後更多! 繼續往下看↓ 最重要的是,Firebase 將作為我們在這個項目中的後端,為我們提供必要的 API 端點來處理我們的身份驗證和數據庫需求。 最後,您將使用 Vue+ Firebase 構建一個功能性調查應用程序。 之後,您可以繼續使用這些相同的流程構建您選擇的任何 Web 應用程序,即使使用自定義後端也是如此。
接下來,您需要在您的機器上安裝 Node 和 npm/yarn。 如果您還沒有這樣做,請按照這些快速指南在您的機器上安裝 yarn 或 npm。 您還需要對本教程的 Vue、Vuex 和 Vue 路由器語法有基本的了解。
本教程的起始文件就在此處,其中包含該項目的基本文件,這裡是已完成演示的 repo。 您可以克隆或下載存儲庫並在終端中運行npm install
。
安裝啟動文件後,您將看到一個歡迎頁面,其中包含註冊和登錄的選項。登錄後,您可以訪問調查。
如果您想完全自己構建這個項目,請隨意創建一個新項目,只需確保將 Vuex、Vue 路由器、Vuelidate 和 axios 安裝到您的 Vue 項目中。 所以讓我們直接進入:
首先,我們需要一個 Firebase 帳戶來設置這個項目,這非常類似於為我們的應用程序創建一個容器,允許我們訪問數據庫、各種身份驗證方式、託管等。一旦你設置它就很簡單了'在 Firebase 網站上。
現在我們有了項目,接下來就是在 Firebase 上設置我們的身份驗證系統和數據庫(實時數據庫)。
- 點擊“認證”選項;
- 設置我們想要的“登錄方法”(在本例中為電子郵件/密碼)。
- 點擊“數據庫”。
- 選擇“實時數據庫”並複制頂部的此鏈接。
當我們想要將數據發送到我們的 firebase 數據庫時,它作為 API 端點將非常有用。
我們將此 API 稱為數據庫 API。 要使用它,您必須在發送時添加您選擇的數據庫的名稱。 例如,要發送到一個名為 user 的數據庫。 您只需在最後添加user.json :
{databaseAPI}/user.json
在此之後,我們將轉到 Firebase auth rest API 文檔以獲取我們的註冊並登錄 API 端點。 在這些端點中,將需要我們項目的 API 密鑰,它可以在我們的項目設置中找到。
驗證
回到我們的代碼,在發送到服務器之前會驗證註冊數據,以確保用戶發送適當的信息。 我們將使用 Vuelidate,這是一個很酷的庫,可以讓 Vue 中的驗證更容易。 首先,將Vuelidate安裝到項目中:
npm i vuelidate
轉到src/components/auth/signup.vue
並在腳本標籤中導入 vuelidate 以及我們需要從庫中獲取的所有必要事件,如下所示。
注意:您可以查看文檔以獲取有關庫和所有可用事件的完整概述。
import { required, email, numeric, minValue, minLength, sameAs } from 'vuelidate/lib/validators'
快速解釋:
價值 | 描述
---|
required | 該值是強制性的 |
email | 值必須是電子郵件 |
numeric | 必須是數字 |
minValue | 用戶可以輸入的最小數值。 |
sameAs | 用於比較兩個值以確保它們相同 |
同時導入 [`axios`](https://github.com/axios/axios) 以便能夠向服務器發送 HTTP 請求:
import axios from 'axios'
在我們繼續之前,我們需要向數據庫添加一些規則,以便能夠按我們應該的方式驗證電子郵件,如下所示:
"read" = "true"
這意味著可以在客戶端不受任何阻礙地讀取數據庫。
"write" = "auth" !== null
除非您是經過身份驗證的用戶,否則您不能在數據庫上寫入。
"Users" = { "onIndex" : ["email"] }
這允許我們查詢帶有 `email` 索引的 `users` 文檔。 也就是說,您可以從字面上過濾數據庫以獲得唯一的電子郵件。 然後添加一個名為 `validations` 的自定義計算屬性,就像我們有方法、計算等一樣。在 `validations` 下,我們將有方法來驗證從需要的 `email` 開始的必要數據,顯然必須是電子郵件. 此外,我們希望能夠告訴用戶電子郵件已被其他人接收,方法是在用戶輸入後使用自定義驗證器中的異步驗證器檢查數據庫,所有這些都由 [vuelidate. ](https://vuelidate.js.org/#sub-asynchronous-validation)
validations : { email: { required, email, unique: val => { if (val === '') return true return axios.get('https://vue-journal.firebaseio.com/users.json?orderBy="email"&equalTo="' + val + '"') .then(res => { return Object.keys(res.data).length === 0 }) } } }
然後在唯一下,使用 axios 查詢數據庫並使用默認的 Object.keys 僅當它的長度為 0 時才返迴響應。對於年齡,您將添加必需的、數字的和分配給 `minVal 的最小值 18 ` 作為它的屬性。
age: { required, numeric, minVal: minValue(18) }
密碼的屬性是必需的,分配給 `minLen` 的最小長度為 6。
password: { required, minLen: minLength(6) }
`confirmPassword` 屬性基本上要和密碼一樣。
confirmPassword: { sameAs: sameAs(vm => { return vm.password }) }
要告訴用戶該電子郵件已被佔用,請使用 `v-if` 來檢查 `unique` 是真還是假。 如果為 true,則表示返回的 Object 的長度為 0,仍然可以使用 email,反之亦然。 以同樣的方式,您可以使用 `v-if` 檢查用戶輸入是否是實際的電子郵件。 對於單個輸入上的所有周圍 div,我們將添加一個無效類,一旦該輸入出現錯誤,該類就會變為活動狀態。 要將驗證事件綁定到 HTML 中的每個輸入,我們使用 [`$touch()`](https://vuelidate.js.org/#sub-without-v-model),如 `email `下面。
<div class="input" :class="{invalid: $v.email.$error}"> <h6 v-if="!$v.email.email">Please provide a valid email address.</h6> <h6 v-if="!$v.email.unique">This email address has been taken.</h6> <input type="email" placeholder="Email" @blur="$v.email.$touch()" v-model="email"> </div>
`Age`、`password` 和 `confirmPassword` 將以與 `email` 類似的方式綁定到它們的 HTML 輸入。 如果任何輸入出現錯誤,我們將使“提交”按鈕處於非活動狀態。
<button type="submit" :disabled="$v.$invalid">create</button>
這是此 vuelidate 部分的完整 [CodePen 示例](https://codepen.io/atanda1/pen/Yzyqrjv)。
## Authentication 這個應用程序是一個 SPA,不會像傳統網站那樣重新加載,所以我們將使用 Vuex 作為我們的單一“事實來源”,讓我們應用程序中的每個組件都知道一般的身份驗證狀態。 我們轉到我們的商店文件,並在操作中創建登錄/註冊方法。 當我們發送用戶數據時收到的響應(`token` 和 `userId`)將存儲在我們的狀態中。 這很重要,因為令牌將用於了解我們在應用程序中的任何時候是否仍在登錄。 `token`、`userId` 和 user 在初始值為 null 的狀態下創建。 我們將在很晚的時候接觸到用戶,但現在,我們將專注於前兩個。
state: { idToken: null, userId: null, user: null }
然後創建突變以在需要時更改狀態。
authUser | 保存令牌和用戶userId |
storeUser | 存儲用戶信息 |
clearAuthData | 將數據擦除回初始狀態 |
mutations: { authUser (state, userData) { state.idToken = userData.token state.userId = userData.userId }, storeUser (state, user) { state.user = user }, clearAuthData (state) { state.idToken = null state.userId = null state.user = null } }
對於註冊/登錄,我們必須為兩者創建單獨的操作,我們將身份驗證請求發送到服務器。 之後,我們來自註冊/登錄的響應(令牌和用戶 ID)被提交給 authUser,並保存在本地存儲中。
signup ({commit, dispatch}, authData) { axios.post('https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=AIzaSyCFr-OMMzDGp4Mmr0t66w2cTGfNazYjptQ', { email: authData.email, password: authData.password, returnSecureToken: true }) .then(res => { console.log(res) commit('authUser', { token: res.data.idToken, userId: res.data.localId }) localStorage.setItem('token', res.data.idToken) localStorage.setItem('userId', res.data.localId) localStorage.setItem('email', res.data.email) dispatch('storeUser', authData) setTimeout(function () { router.push('/dashboard') }, 3000) }) .catch(error => console.log(error)) }
login ({commit}, authData) { axios.post('https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=AIzaSyCFr-OMMzDGp4Mmr0t66w2cTGfNazYjptQ', { email: authData.email, password: authData.password, returnSecureToken: true }) .then(res => { console.log(res) localStorage.setItem('token', res.data.idToken) localStorage.setItem('userId', res.data.localId) localStorage.setItem('email', res.data.email) commit('authUser', { token: res.data.idToken, userId: res.data.localId }) router.push('/dashboard') }) .catch(error => console.log(error.message)) }
但這裡有一個棘手的部分,我們將對註冊操作做的特別是只發送要在身份驗證數據庫中註冊的電子郵件和密碼。 實際上,我們無權使用此身份驗證數據庫中的數據,並且除了電子郵件/密碼之外,我們沒有發送任何註冊數據。 所以我們要做的是創建另一個動作來將完整的註冊數據發送到另一個數據庫。 在這個單獨的數據庫文檔中,我們可以完全訪問我們選擇保存在那裡的所有信息。 我們將把這個新動作稱為 `storeUser` 我們然後轉到我們的註冊操作並將包含我們的註冊數據的整個對象分派到我們現在可以通過 `storeUser` 訪問的數據庫。 **注意:**出於安全原因,您可能不希望將用戶密碼與 `storeUser` 一起發送到數據庫。
storeUser ({ state}, userData) { if (!state.idToken) { return } axios.post('https://vue-journal.firebaseio.com/users.json' + '?auth=' + state.idToken, userData) .then(res => console.log(res)) .catch(error => console.log(error)) } }
`storeUser` 在發佈到數據庫時使用我們新獲得的令牌和數據庫 API 添加查詢。 這是因為我們無法寫入我們的數據庫,除非我們使用我們的證明(令牌)進行了身份驗證。 這就是我們一開始給 Firebase 的規則,記得嗎?
“write” = “auth” !== null
註冊/登錄操作的完整代碼就在 [這裡](https://codepen.io/atanda1/pen/mdePKqj)。 然後在 `onSubmit` 方法中將註冊和登錄從它們的組件分派到商店中的相應操作。
methods : { onSubmit () { const signupData = { email : this.email, name : this.name, age : this.age, password : this.password, confirmPassword : this.co nfirmPassword } this.$store.dispatch('signup', signupData) } } }
**注意:** `signupData` 包含表單的數據。
methods : { onSubmit = { const formData = { email : this.email, password : this.password } this.$store.dispatch('login', {email: formData.email, password: formData.password}) } }
## AuthGuard 需要 AuthGuard 來防止未登錄的用戶訪問他們將發送調查的儀表板。 轉到路由文件並導入我們的商店。
import store from './store'
在路由中,轉到儀表板的路徑並添加以下內容:
const routes = [ { path: '/', component: WelcomePage }, { path: '/signup', component: SignupPage }, { path: '/signin', component: SigninPage }, { path: '/dashboard', component: DashboardPage, beforeEnter (to, from, next) { if (store.state.idToken) { next() } else { next('/signin') } } } ]
所有這一切都是為了檢查狀態中是否存在令牌,如果是,我們授予對儀表板的訪問權限,反之亦然。 ## LogOut 為了創建我們的註銷選項,我們將使用我們之前在 `mutations` 下創建的 `clearAuth`,它只是將 `token` 和 `userId` 設置為 `null`。 我們現在創建一個新的 `logout` `action` ,提交到 `clearAuth`,刪除本地存儲並添加 `router.replace('/')` 以完全重定向用戶。
actions: { logout ({commit}) { commit('clearAuth') localStorage.removeItem('token') localStorage.removeItem('userId') router.replace('/') } }
在標頭組件中,我們有一個 `onLogout` 方法,它在商店中調度我們的註銷操作。
methods: { onLogout() { this.$store.dispatch('logout') } }
然後,我們向觸發 `onLogout` 方法的按鈕添加一個`@click`,我們可以在 [這裡](https://codepen.io/atanda1/pen/jObqKNd) 中看到。
<ul @click="onLogout">Log Out</ul>
## UI_State 現在我們已經授予了對儀表板的條件訪問權限,下一步是將其從導航欄中刪除,因此只有經過身份驗證的用戶才能查看它。 為此,我們將在 `getters` 下添加一個名為 `ifAuthenticated` 的新方法,該方法檢查我們狀態中的令牌是否為空。 當有令牌時,它表明用戶已通過身份驗證,我們希望他們在導航欄上看到調查儀表板選項。
getters: { isAuthenticated (state) { return state.idToken !== null } }
之後,您返回到 header 組件並在 computed 下創建一個方法 `auth`,該方法在我們剛剛在 store 中創建的 `getter` 中分派給我們的 `isAuthenticated`。 這樣做是如果沒有令牌,`isAuthenticated` 將返回 false,這意味著 `auth` 也將為 null,反之亦然。
computed: { auth () { return this.$store.getters.ifAuthenticated } }
之後,我們在 HTML 中添加一個 `v-if` 來檢查 `auth` 是否為空,確定該選項是否會顯示在導航欄上。
<li v-if='auth'> <router-link to="/dashboard">Dashboard</router-link> </li> <li v-if='!auth'> <router-link to="/signup">Register</router-link> </li> <li v-if='!auth'> <router-link to="/signin">Log In</router-link> </li>
- 您將在 [此處](https://codepen.io/atanda1/pen/QWjNxyo) 找到 UI 狀態部分的完整代碼。
自動登錄
當我們重新加載我們的應用程序時,我們會丟失數據並退出,不得不重新開始。 這是因為我們的 token 和 Id 存儲在 Vuex 中,它是 javascript,這意味著我們的應用程序在刷新時會通過瀏覽器重新加載。
最後,我們要做的是在本地存儲中檢索令牌。 通過這樣做,無論何時刷新窗口,我們都可以在瀏覽器上擁有用戶的令牌,並且在令牌仍然有效的情況下,有一個方法自動登錄我們的用戶。
創建了一個名為AutoLogin
的新actions
方法,我們將從本地存儲中獲取令牌和userId
,並將我們的數據提交給突變中的authUser
方法。
actions : { AutoLogin ({commit}) { const token = localStorage.getItem('token') if (!token) { return } const userId = localStorage.getItem('userId') const token = localStorage.getItem('token') commit('authUser', { idToken: token, userId: userId }) } }
然後我們去我們的 App.vue 並編寫一個created
方法,每次加載應用程序時都會從我們的商店調度autoLogin
。
created () { this.$store.dispatch('AutoLogin') }
Fetch_User_Data
我們希望通過顯示用戶名來歡迎儀表板上的用戶。 因此,創建了另一個名為fetchUser
的操作,它首先像往常一樣檢查是否有令牌。 然後,它繼續從本地存儲中獲取電子郵件,並像之前通過電子郵件驗證所做的那樣查詢數據庫。
這將返回一個對象,其中包含最初在註冊期間提交的用戶數據。 然後我們將此對象轉換為一個數組並將其提交給最初創建的storeUser
突變。
fetchUser ({ commit, state}) { if (!state.idToken) { return } const email = localStorage.getItem('email') axios.get('https://vue-journal.firebaseio.com/users.json?orderBy="email"&equalTo="' + email + '"') .then(res => { console.log(res) // const users = [] console.log(res.data) const data = res.data const users = [] for (let key in data) { const user = data[key] user.id = key users.push(user) console.log(users) } commit('storeUser', users[0]) }) .catch(error => console.log(error)) }
之後,我們創建另一個名為user
的 getter,它返回已經通過storeUser
提交的state.user
。
getters: { user (state) { return state.user }, isAuthenticated (state) { return state.idToken !== null } }
回到儀表板,我們創建一個名為name
的新計算方法,僅當用戶存在時才返回state.user.name
。
computed: { name () { return !this.$store.getters.user ? false : this.$store.getters.user.name } }, created () { this.$store.dispatch('fetchUser') } }
我們還將添加created
計算屬性以在頁面加載後調度fetchUser
操作。 然後,我們在 HTML 中使用v-if
來顯示名稱(如果名稱存在)。
<p v-if="name">Welcome, {{ name }} </p>
發送_調查
為了發送調查,我們將創建一個postData
操作,該操作使用數據庫 API 將數據發送到數據庫,並使用令牌顯示用戶已登錄的服務器。
postData ({state}, surveyData) { if (!state.idToken) { return } axios.post('https://vue-journal.firebaseio.com/survey.json' + '?auth=' + state.idToken , surveyData) .then(res => { console.log(res) }) .catch(error => console.log(error)) }
我們回到儀表板組件並將數據分派到存儲中的postData
操作。
methods : { onSubmit () { const postData = { price: this.price, long: this.long, comment: this.comment } console.log(postData) this.$store.dispatch('postData', postData) } }
有了它,我們在演示應用程序中實現了許多有用的功能,同時與我們的 Firebase 服務器通信。 希望您將在下一個項目中使用這些強大的功能,因為它們對於當今構建現代 Web 應用程序非常重要。
如果您有任何問題,可以在評論部分留下它們,我很樂意為您一一解答!
其他可能被證明有用的資源包括:
- 要了解有關 Firebase 及其提供的其他服務的更多信息,請查看 Chris Esplin 的文章“什麼是 Firebase?”
- Vuelidate 是一個非常不錯的庫,你應該深入研究一下。 你應該通讀它的文檔以獲得完整的洞察力。https://vuelidate.js.org/。
- 您也可以自行探索 axios,特別是如果您想在更大的項目中使用它。