React Native中指定了无效的令牌(JWT身份验证)

7

我正在尝试将react迁移到react native,因为我想在制作网站之后制作应用程序,但是由于react-native的某些语法似乎与典型的react不同。我对react-native还是比较新手,所以我认为问题出在react-native处理令牌的方式上。Graphql查询和mutations似乎运行良好,但我的jwt令牌和身份验证似乎受到了影响。

代码


apolloClient.js

import { ApolloClient } from "@apollo/client/core";
import { InMemoryCache } from "@apollo/client/cache";
import { setContext } from "@apollo/client/link/context";
import { createUploadLink } from "apollo-upload-client";
import AsyncStorage from "@react-native-async-storage/async-storage";

const httpLink = createUploadLink({
  uri: "http://localhost:5001/graphql",
});

const authLink = setContext(async () => {
  const token = await AsyncStorage.getItem("jwtToken");
  return {
    headers: {
      Authorization: token ? `Bearer ${token}` : "",
    },
  };
});

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
});

export default client;

auth.js

import { useReducer, createContext, useEffect } from "react";
import jwtDecode from "jwt-decode";
import AsyncStorage from "@react-native-async-storage/async-storage";

const initialState = {
  user: null,
};

const getToken = async () => {
  try {
    const storedToken = await AsyncStorage.getItem("jwtToken");

    if (storedToken !== null) {
      const decodedToken = jwtDecode(AsyncStorage.getItem("jwtToken"));
      const expirationData = decodedToken.exp;
      const currentDate = Date.now / 1000;
      if (expirationData >= currentDate) {
        initialState.user = decodedToken;
      }
    }
  } catch (err) {
    console.log(err);
  }
};

getToken();

const AuthContext = createContext({
  user: null,
  login: (userData) => {},
  logout: () => {},
});

function authReducer(state, action) {
  switch (action.type) {
    case "LOGIN":
      return {
        ...state,
        user: action.payload,
      };
    case "LOGOUT":
      return {
        ...state,
        user: null,
      };
    default:
      return state;
  }
}

function AuthProvider(props) {
  const [state, dispatch] = useReducer(authReducer, initialState);
  //   const router = useRouter();

  function login(userData) {
    AsyncStorage.setItem("jwtToken", userData.token);

    dispatch({
      type: "LOGIN",
      payload: userData,
    });
  }

  function logout() {
    AsyncStorage.removeItem("jwtToken");
    // toast.error("Logged out successfully", shortToastConfig);
    dispatch({ type: "LOGOUT" });
  }

  useEffect(() => {
    if (state.user) {
      const decodedToken = jwtDecode(AsyncStorage.getItem("jwtToken"));
      const timeLeft = decodedToken.exp * 1000 - Date.now();
      setTimeout(() => {
        dispatch({
          type: "LOGOUT",
        });
        AsyncStorage.removeItem("jwtToken");
        // toast.error("Session Expired", shortToastConfig);
        // router.push("/login");
      }, timeLeft);
    }
  }, [state]);

  return (
    <AuthContext.Provider
      value={{ user: state.user, login, logout }}
      {...props}
    />
  );
}

export { AuthContext, AuthProvider };

App.js

import React from 'react';
import { ApolloProvider } from '@apollo/client';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { useFonts } from 'expo-font';
import {
  MyShop,
  SignIn,
  SignUp,
  Navigator,
  Chats,
  ShoppingCart,
  Settings,
  // Country,
  Me,
  Home,
  Categories,
  SubCategories,
  Products,
} from './screens';
import { AuthProvider } from './context/auth';
import client from './context/apolloClient';

export default function App() {
  LogBox.ignoreAllLogs(true);
  const Stack = createStackNavigator();

  return (
    <ApolloProvider client={client}>
      <AuthProvider>
        <NavigationContainer>
          <Stack.Navigator
            screenOptions={{ headerShown: false }}
            initialRouteName="Navigator"
          >
            <Stack.Screen
              name="Navigator"
              component={Navigator}
              options={{ gestureEnabled: false }}
            />
            <Stack.Screen
              name="Home"
              component={Home}
              options={{ gestureEnabled: false }}
            />
            {/* <Stack.Screen
            name="Country"
            component={Country}
            options={{ gestureEnabled: false }}
          /> */}
            <Stack.Screen
              name="SignIn"
              component={SignIn}
              options={{ gestureEnabled: false }}
            />
            <Stack.Screen
              name="SignUp"
              component={SignUp}
              options={{ gestureEnabled: false }}
            />
            <Stack.Screen
              name="Me"
              component={Me}
              options={{ gestureEnabled: false }}
            />
            <Stack.Screen name="MyShop" component={MyShop} />
            <Stack.Screen name="ShoppingCart" component={ShoppingCart} />
            <Stack.Screen name="Chats" component={Chats} />
            <Stack.Screen name="Settings" component={Settings} />

            {/* Other Pages */}
            <Stack.Screen name="Categories" component={Categories} />
            <Stack.Screen name="SubCategories" component={SubCategories} />
            <Stack.Screen name="Products" component={Products} />
          </Stack.Navigator>
        </NavigationContainer>
      </AuthProvider>
    </ApolloProvider>
  );
}

错误信息:

在此输入图片描述

如果您需要更多的代码,请让我知道并透明地编辑这篇文章。如果您不理解我的意思或想要表达的内容,请在下面发表评论,如果可以,请向我提供有关React Native的一些提示和建议,以便我可以更多地学习。谢谢!


1
嘿,Mohammad,你能具体说明一下你的问题吗?描述一下哪些部分不起作用。你是如何向应用程序发送请求的,你得到了什么响应,你期望得到什么响应?至于代码- SO社区通常不需要更多的代码,但重要的是你分享代码的相关部分。太多的代码也不好。 - Michal Trojanowski
这是所有的“客户端”代码,但错误发生在服务器上,由客户端看到。很可能请求本身从未提供必要的身份验证,因此403完全有效。整个问题就像没有飞机飞行并问为什么你即将撞上的树是枫树而不是你预期的橡树林。这只是一个React问题,可能甚至不是服务器问题,也许它只是没有做你假设的事情,比如提供认证。 - Stof
3个回答

7

您正在使用的AsyncStorage函数是异步函数。您需要等待它们被解析。

用法:

const getData = async () => {
try {
  const value = await AsyncStorage.getItem('@storage_Key')
  if(value !== null) {
    // value previously stored
  }
} catch(e) {
  // error reading value
}

}

// In your case the key is `jwtToken`
const token = await AsyncStorage.getItem("jwtToken");

更多信息和源代码在此处:

https://react-native-async-storage.github.io/async-storage/docs/usage

尝试使用硬编码的JWT令牌使您的代码正常工作,然后添加异步存储功能。


我��前已经研究过异步函数,只是没有更新我的工作。它可以工作,但当我使用后端登录时,仍然显示无效的令牌,即使在我点击登录按钮之前,它就已经显示了无效的令牌。如果您能帮我解决这个问题,我会更新我的代码。 - Mohammad Khan

1
如果你要翻译的代码是你目前正在使用的代码,那么你在使用异步函数方面仍然存在问题。例如,在auth.js文件中:
const storedToken = await AsyncStorage.getItem("jwtToken");

if (storedToken !== null) {
   const decodedToken = jwtDecode(AsyncStorage.getItem("jwtToken"));
...
}

您已经以正确的方式获取了存储的令牌。但是,然后您调用了jwtDecode,而不是传递存储的令牌,而是传递了一个异步函数。 jwtDecode 需要一个字符串。至少有两个地方这样做。您应该传递存储的令牌:

const decodedToken = jwtDecode(storedToken);

1

当从AsyncStorage获取内容时,您需要等待解决Promise。

AuthProvider中将useEffect更改为

useEffect(() => {
  if (state.user) {
    logout();
  }
}, [state]);

在哪里

const logout = async () => {
  const decodedToken = jwtDecode(await AsyncStorage.getItem("jwtToken"));
  const timeLeft = decodedToken.exp * 1000 - Date.now();
  setTimeout(() => {
    dispatch({
      type: "LOGOUT",
    });
    AsyncStorage.removeItem("jwtToken");
    // toast.error("Session Expired", shortToastConfig);
    // router.push("/login");
  }, timeLeft);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接