接著就來做登入/註冊/登出功能吧!但因為這個功能較為複雜,讓我們分為兩天來寫。今天先講個大綱,明天再來實作!
在這邊我們使用 token based 的認證方式,所以我們會透過登入從 Server 取得一個 token ,接著我們會將 token 存入瀏覽器的 local storage 裡面。 local storage 裡面有了 token 後,我們要設定在之後的 query 都要帶上 token 讓 Server 可以認證使用者身份,以決定這個 query 是否可以繼續。
為了要實作登入功能,讓我們換一個 server url ,可以使用我寫好的 Apollo LaunchPad 範例 ,或直接帶入新的 url: https://vv49414q93.lp.gql.zone/graphql 。
首先是登入,我們會在前端新增一個頁面來使用 login
mutation 。
得到 token 後使用 localStorage.setItem('x-token', data.login.token);
將 token 存在 local storage 內。
接著要如何在每次 request 的 header 都帶上 token 呢 ? 這邊我們要來設定 Apollo Client , 可搭配 官網說明 參考,裡面有一個 request
的設定選項可以讓我們在每次 request 送出時都套用一樣的設定。
const client = new ApolloClient({
uri: 'https://vv49414q93.lp.gql.zone/graphql',
request: async operation => {
const token = await localStorage.getItem('x-token');
if (token) {
operation.setContext({
headers: {
'x-token': token
}
});
}
}
});
可以看到這邊我們先試著從 local storage 去取得 x-token
,如果 token 存在,就把它加進 request 的 headers 。
另外登出非常簡單,直接清掉 local storage 的紀錄即可: localStorage.removeItem('x-token')
,當 token 消失後,使用者就只能再次登入得到新的 token 。
關於 Authorization 其實非常彈性,因為今天可能文章列表僅提供本人觀看、會員觀看或是供大眾觀看,但是新增文章、刪除文章一定都是本人才可以操作,因此這邊就會造成每個 component 的權限不盡相同,連帶就必須有策略地去做管理。
這邊我選擇使用 higher order function 來實作,分為 WithUser
與 RequireAuth
,兩著皆會使用 query 搭配 local storage 的 token 去取得目前登入者資訊。
query CurrentUser {
me {
id
name
email
}
}
WithUser
可以讓 wrapped component 從 props.user
得到目前登入者的資訊 ; 而 RequireAuth
跟 WithUser
一樣,只是如果沒有得到登入者資料 (沒有 token 或 token 不合法),就會直接強制轉到登入畫面。
舉個例子,我接下來會新增一個 Header
Component ,當使用者尚未登入時,就會顯示 "Signup" 與 "Login" 兩個按鈕,而如果使用者已經登入了,則是顯示 "Logout" 按鈕,這邊就會用到 WithUser
來判斷顯示方式。而且像是刪除文章的按鈕也是需要 WithUser
傳入已登入使用者資訊來判斷是否顯示。
再來,像是新增文章頁面,就一定要得是已登入使用者才可以訪問,因此會用到 RequireAuth
來幫忙判斷,如果尚未登入就應該直接跳到登入頁面~