You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native中JWT认证出现Invalid Token Specified问题求助

JWT认证报错Invalid Token Specified排查及React Native学习建议

问题背景

将React Web项目迁移至React Native开发移动端应用,GraphQL查询及mutation功能正常,但JWT令牌认证失效,系统提示Invalid Token Specified。


相关代码

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);

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

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

  function logout() {
    AsyncStorage.removeItem("jwtToken");
    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");
      }, 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,
  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="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>
  );
}

报错信息

Invalid Token Specified错误截图


问题排查与修复

核心错误点

  1. 异步操作处理不当

    • getToken是异步函数,直接调用无法等待其执行完成,导致initialState.user初始化时无法正确赋值。
    • getToken及useEffect中,调用jwtDecode(AsyncStorage.getItem("jwtToken"))时,AsyncStorage.getItem是异步方法,返回Promise而非token字符串,直接传给jwtDecode会导致解码失败,生成无效的token数据。
    • Date.now是函数,错误写成Date.now / 1000,导致currentDate为函数引用,与expirationData的对比永远不成立。
  2. 状态初始化逻辑错误
    直接修改initialState无法触发组件状态更新,应该通过dispatch来更新用户状态。

修正后的auth.js关键代码

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

  // 初始化验证token
  useEffect(() => {
    const initAuth = async () => {
      try {
        const storedToken = await AsyncStorage.getItem("jwtToken");
        if (storedToken) {
          const decodedToken = jwtDecode(storedToken);
          const currentDate = Date.now() / 1000;
          if (decodedToken.exp >= currentDate) {
            dispatch({ type: "LOGIN", payload: decodedToken });
            // 设置过期自动登出定时器
            const timeLeft = decodedToken.exp * 1000 - Date.now();
            setTimeout(logout, timeLeft);
          } else {
            await AsyncStorage.removeItem("jwtToken");
          }
        }
      } catch (err) {
        console.error(err);
        await AsyncStorage.removeItem("jwtToken");
      }
    };
    initAuth();
  }, []);

  function login(userData) {
    AsyncStorage.setItem("jwtToken", userData.token);
    const decodedToken = jwtDecode(userData.token);
    dispatch({ type: "LOGIN", payload: decodedToken });
    // 设置过期定时器
    const timeLeft = decodedToken.exp * 1000 - Date.now();
    setTimeout(logout, timeLeft);
  }

  // ... logout函数及其他代码保持不变
}

额外检查项

  • 确认apolloClient.js中Authorization头格式符合后端要求,Bearer 后需有空格,且token无多余空格或换行。
  • 登录时验证userData.token是后端返回的完整JWT字符串,未被截断或篡改。

React Native学习建议

  • 夯实基础:优先掌握RN核心组件(View、Text、ScrollView等)和原生API(AsyncStorage、Permissions等),熟悉Flexbox布局的跨平台特性。
  • 状态管理循序渐进:从Context API入门,熟练后再尝试Redux、Zustand等工具,避免过度复杂化。
  • 掌握调试技巧:用RN Debugger配合Chrome DevTools调试网络请求,查看请求头是否正确携带token;用console.log快速验证变量值。
  • 关注平台差异:iOS与Android在组件样式、系统API上存在差异,学会用Platform模块处理适配逻辑。
  • 利用生态工具:用Expo简化开发、打包流程;熟练使用React Navigation实现路由管理,这是RN生态的标准导航方案。
  • 迁移注意事项:RN无DOM环境,Web项目中的document、window等API无法使用,需替换为RN的对应实现。

内容的提问来源于stack exchange,提问作者Mohammad Khan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 04:27:15