iT邦幫忙

2024 iThome 鐵人賽

DAY 22
0

昨天我們使用 chatgpt 幫我們在 react native 裡面串接 api,不過老實說有點太粗糙。我今天試了一天,如果使用AI要求他修改成想要的形式,會花滿多時間再和他溝通的。我花了一些時間來改寫一下程式碼。今天來說明一下比較偏架構的部分。

Token

前面有說過,我們使用 jwt 認證來實作。當登入的時候,會回傳只屬於 user 的 token。在後面請求所有資源或操作的時候都會需要驗證這個 token。所以登入後會需要把這個 token存起來。

存放

那 token 要放哪裡呢?如果單純放在 redux的 state 裡面的話。這樣你下次打開就沒有 token了。不然就是每次都要不然就是每次使用都要在登入一次,這樣太不合邏輯了吧。

AsyncStorage

APP 也有個像是網頁 localStorge的東西。 AsyncStorage ,要存東西的時候可以使用 setItem 取出使用 getItem

import AsyncStorage from '@react-native-community/async-storage';
.....

await AsyncStorage.setItem('userToken', result.accessToken);
await AsyncStorage.getItem('userToken', result.accessToken);

登入後存放

那什麼時後存放呢,可以在登入後成功取得回傳資料後就直接在 Action 存放在 Stroage裡面。這樣,下次打開 App 依然可以使用該 Token 來進行身份驗證。

在 userAction中執行存放 token的動作。
userAction.ts

export const loginUser = createAsyncThunk(
  'user/login',
  async (data: {username: string; password: string}, thunkAPI) => {
    try {
      const response = await apiService.loginUser(data);
      await AsyncStorage.setItem('userToken', result.accessToken);
      return response.data;
    } catch (error: unknown) {
      const errorMessage = (error as ErrorResponse)?.message || '未知錯誤';
      return thunkAPI.rejectWithValue(errorMessage);
    }
  },
);

取得

取得 token的方法可以用 getItem。

const getToken = async () => {
  try {
    const userToken = await AsyncStorage.getItem('userToken');
    return userToken ? userToken : null;
  } catch (error) {
    console.error('Failed to retrieve token:', error);
    return null;
  }
};

Authorization

認證的部分使用放在 Header 裡面請求的 Bearer token ,但要在每個 api call 的時候都寫取得 Token 也不太合理吧。一樣的程式碼重複寫,不太好。因此我們可以利用 Axios 攔截器 自動將 Token 附加到請求 Header 中。

Axios 攔截器的設計:

instance.interceptors.request.use(
  async (config: InternalAxiosRequestConfig) => {
    const token = await getToken();
    if (token) {
      config.headers = new AxiosHeaders({
        ...config.headers,
        Authorization: `Bearer ${token}`, 
      });
    }
    return config;
  },
  error => {
    return Promise.reject(error);
  },
);

[!NOTE]
axiosfetch 都是用來發送 HTTP 請求的工具。大部分時間都差不多,不過因為我實際專案使用的是 Axios。

  • axios 是一個外部庫,內建有許多便捷功能,如自動解析 JSON、錯誤處理(HTTP 4xx、5xx 會自動觸發錯誤處理)、攔截器、請求取消等。
  • fetch 是瀏覽器原生 API,需要手動處理 JSON 回應與錯誤,對於網路錯誤會觸發錯誤處理,但對於 HTTP 4xx、5xx 狀態碼則需要自行處理。

[!Hint]
我們明天會來講一下 Api redux 串接設計的部分,會詳細講一下。 我們會寫一個axios config這個檔案來處理所有 api call的行為。

確認是否登入過

Token搭配Storage也可以用來確認登入狀態,透過檢查 AsyncStorage 中是否存有 Token 來確認使用者的登入狀態,並根據結果決定是否讓使用繼續瀏覽頁面或是強制導向登入頁。同時檢查用戶是否已經登入,若未登入,則導向登入頁面。

routers.tsx

export const Router = ({initialRoute}: {initialRoute: string}) => {
  const navigation = useNavigation();

  const checkToken = async () => {
    try {
      const userToken = await AsyncStorage.getItem('userToken');
      if (!userToken) {
        // 顯示警告
        Alert.alert(
          '未登入', // 標題
          '請先登入以繼續', // 訊息
          [
            {
              text: '確認',
              onPress: () => {
                navigation.reset({
                  index: 0,
                  routes: [{name: RouteNames.Login} as never],
                });
              },
            },
          ],
          {cancelable: false},
        );
      }
    } catch (e) {
      console.error(e);
    }
  };

  return (
    <Stack.Navigator initialRouteName={initialRoute}>
      {screens.map(screen => (
        <Stack.Screen
          key={screen.name}
          name={screen.name}
          component={screen.component}
          options={screen.options}
          listeners={
            screen.name !== RouteNames.Login &&
            screen.name !== RouteNames.Register
              ? {focus: checkToken}
              : undefined
          }
        />
      ))}
    </Stack.Navigator>
  );
};

過期

JWT Token 會設置有效期限,因此我們需要在每次發送 API 請求前檢查 Token 是否過期。如果過期,應該移除 Token 並強制用戶重新登入。

先寫一個可以確認 token是否過期的函數

import jwtDecode from 'jwt-decode';

const isTokenExpired = (token: string): boolean => {
  if (!token) return true; // 如果沒有 token,視為過期
  const decoded: any = jwtDecode(token);
  const currentTime = Date.now() / 1000; // 當前時間(秒)
  return decoded.exp < currentTime; // 如果過期,返回 true
};

再來寫一個 token 過期的話直接移除 Storage 裡面的 token 然後直接回到登入頁面的函數

const checkAndRemoveToken = async () => {
  const token = await AsyncStorage.getItem('userToken');
  if (isTokenExpired(token)) {
    await AsyncStorage.removeItem('userToken'); // 移除過期的 token
    // 這裡可以重定向用戶到登入頁面
    navigation.reset({
      index: 0,
      routes: [{ name: RouteNames.Login }],
    });
  }
};

再來在 Axios 攔截器中使用,檢查 Token 是否過期並決定是否允許 API 請求

apiClient.interceptors.request.use(async (config) => {
  const token = await AsyncStorage.getItem('userToken');
  if (isTokenExpired(token)) {
    await AsyncStorage.removeItem('userToken');
    // 這裡可以重定向用戶到登入頁面
    navigation.reset({
      index: 0,
      routes: [{ name: RouteNames.Login }],
    });
  } else {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

登入-取得資源流程

  • 使用者提交登入憑證:當使用者嘗試登入時,系統會將使用者提供的帳號和密碼傳送給後端 API。
  • API 回傳 JWT Token:如果登入資訊正確,伺服器會生成一個 JWT Token 並返回給前端。
  • 將 Token 儲存到 AsyncStorage:這個 Token 會儲存在 AsyncStorage 中,以便後續操作使用。
  • 每次 API 請求時檢查 Token:當使用者執行任何需要授權的操作時,Axios 攔截器會自動在請求中加上 Bearer Token
  • Token 驗證與過期檢查:Axios 攔截器也會檢查這個 Token 是否過期,若 Token 過期,使用者會被重定向至登入頁面;若 Token 有效,系統將帶著 Token 發送 API 請求。
  • 伺服器處理請求:伺服器會驗證 Token,確認使用者身份,然後回應所需的數據。

結語

今天我們介紹了 token 在 app 的用途和概念的實作的部分,雖然要用 AI幫你做出來也是可以,但前提是要先知道要怎麼引導他,還有要要驗證。

不過我會覺得自己修改比較快😅

明天會和大家說一下 串接 api的部分的設計 slice , action , api , axios 設定 還有錯誤處理的方式。我覺得架構的基本功如果寫得好,後面開發體驗會比較舒服。

相對來說,如果你專案的架構寫得好的話,AI幫你生成code的時候也會照著你的架構實作。是不是有點像在帶新人的感覺🙂


上一篇
[Day21]API 串接 Redux Toolkit:串接會員系統與頁面實作
下一篇
[Day23] React native call api 後發生了什麼事?架構設計與流程說明
系列文
30天 使用chatGPT輔助學習APP完成接案任務委託30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言