需要帮助尝试在 React Native 中刷新我的令牌

Nic*_*lva 5 javascript token local-storage react-native

我认为这是一个简单的任务,存储我的令牌,设置一个计时器并在计时器到期时获取令牌,我错了,在观看并阅读了几篇有关如何解决此问题的文章后,我现在非常迷茫,我需要帮助存储我的令牌(或两者,用户名数据加令牌?不再确定),并在令牌过期时刷新。

是的,我在堆栈溢出中看到了很多与此相关的问题,但其中许多与特定问题有关,而不是如何去做。

我的应用程序通过来自 api(net core 2.0)的 microsoft graph 连接到 office 365。

在我的应用程序中,我使用此代码从 api 获取数据并传递我的用户名和密码参数

  async ApiLogin(loginRequestObject: LoginRequest) {
    var serviceResult = new ServiceResult();
    await NetInfo.fetch().then(async state => {
      var param = JSON.stringify(loginRequestObject);
      if (state.isConnected) {
        try {
          await fetch(ServiceProperties.URLauthentication, {
            method: 'POST',
            headers: {
              Accept: 'application/json',
              'Content-Type': 'application/json',
            },
            body: param,
          })
            .then(response => {
              return response.json();
            })
            .then(responseJson => {
              if (JSON.stringify(responseJson) != null) {
                serviceResult.Success = true;
                serviceResult.Message = 'Service Authentication ok';
                serviceResult.ResponseBody = responseJson;
                serviceResult.StatusCode = 0;
              } else {
                serviceResult.Success = false;
                serviceResult.Message = 'Service Authentication not ok';
                serviceResult.ResponseBody = null;
                serviceResult.StatusCode = -100;
              }
            });
        } catch (error) {
          serviceResult.Success = false;
          serviceResult.Message = 'Service Authentication not ok';
          serviceResult.ResponseBody = null;
          serviceResult.StatusCode = -999;
        }
      } else {
        serviceResult.Success = false;
        serviceResult.Message = 'Service internet not ok';
        serviceResult.ResponseBody = null;
        serviceResult.StatusCode = -1;
      }
    });
    console.log(JSON.parse(serviceResult.ResponseBody));
    return serviceResult;
  }
Run Code Online (Sandbox Code Playgroud)

结果是这样。

{"Username":"sensitive data","DisplayName":"sensitive data","GivenName":"sensitive data","SurName":"sensitive data","Email":"sensitive data","Token":"ZSI6Im42aGRfdDVGRHhrSzBEVklJUXpxV09DWHZ4dWc0RlhWVkI4ZVJ6dEFsWDAiLCJhbGciOiJSUzI1NiIsIng1dCI6IlNzWnNCTmhaY0YzUTlTNHRycFFCVEJ5TlJSSSIsImtpZCI6IlNzWnNCTmhaYm5ldC8zOTBmODU5NS1kZTFlLTRmNmQtYTk1NC0yNWY2N5MjkwMTYsImV4cCI6MTU5MjkzMjkxNiButVBqe3E3QwcBr1P0G_dWyC9ASQU0psGDPnsQPHp0T070ROZ_mcPitgquNfsO5JZ8-o056l_aePhXSMO7bHWmUBbVn7TA1UoYIz3lAoOzvE6juadve4aU3goeaBj8PIrhG0M2zEEfKgOL1Al9MSU1GGUmRW9dBofeA4e1cGmlGQrUKnt73n0sHap6","PhotoBase64":null}
Run Code Online (Sandbox Code Playgroud)

这几乎就是我得到的全部,目前,我在这个应用程序上使用了异步存储,但至少可以说只存储一个带有“无用”数据的对象,我不确定异步存储是否适合这与否,如果没有,我该怎么办?

编辑:阅读更多内容后,我发现我需要请求第二个令牌,来自 microsoft graph https://massivescale.com/microsoft-v2-endpoint-primer/的刷新令牌仍然需要有关如何存储数据的帮助并在过期时刷新令牌,

编辑 2:不幸的是,我既没有从 api 中获得刷新令牌,也没有获得 expiresAt 值

Gen*_*aev 6

我无法帮助该特定身份验证提供程序(从未与 Office 365 合作过),但这是您需要遵循的一般步骤:

  1. 发送请求以获取访问和刷新令牌
  2. 将令牌存储在通过重新加载/重启来持久化数据的存储中(对于 Web,它将是 localStorage,对于 RN sqlite 或 asyncstorage 或您使用的任何东西)
  3. 保存令牌和身份验证状态,表明它可用于您的所有组件(Redux、Context API 甚至您自己的解决方案)。当用户验证、未验证或令牌过期时,需要显示/隐藏应用程序的一部分
  4. 您需要以某种方式知道令牌何时到期(不能说如何去做,但 API 文档应该有一些信息)并使用setTimeout以刷新
  5. 当您刷新令牌时,您应该保留它(参见 n.2)并更新全局身份验证状态(参见 n.3)
  6. 当应用程序刚刚(重新)启动时,检查您是否在存储中保留了访问/刷新令牌(参见 n.2)并相应地更新全局身份验证状态(参见 n.3)
  7. 您的路由应该对身份验证状态更改做出反应(请参阅路由库的文档,有关受保护/身份验证的路由的内容)。显示敏感内容的组件也应该对身份验证状态更改做出反应。

这是我的 Reactjs 身份验证解决方案(不幸的是没有 RN 示例),它使用 JWT 根据我自己的 API 对客户端进行身份验证。这种情况下的访问令牌也是刷新令牌。我使用一种没有 Redux 的方法,只使用纯 React 和 JS。我希望这会帮助你。

import { useCallback, useState, useEffect } from "react";
import JWT from "jsonwebtoken";
import { ENV } from "../config";
import { useLanguageHeaders } from "./i18n";

const decodeToken = (token) =>
  typeof token === "string" ? JWT.decode(token) : null;

//This class is responsible for authentication, 
//refresh and global auth state parts
//I create only one instance of AuthProvider and export it, 
//so it's kind of singleton
class AuthProvider {
  //Getter for _authStatus
  get authStatus() {
    return this._authStatus;
  }

  constructor({ tokenEndpoint, refreshEndpoint, refreshLeeway = 60 }) {
    this._tokenEndpoint = tokenEndpoint;
    this._refreshEndpoint = refreshEndpoint;
    this._refreshLeeway = refreshLeeway;
    //When app is loaded, I load token from local storage
    this._loadToken();
    //And start refresh function that checks expiration time each second
    //and updates token if it will be expired in refreshLeeway seconds
    this._maybeRefresh();
  }

  //This method is called in login form
  async authenticate(formData, headers = {}) {
    //Making a request to my API
    const response = await fetch(this._tokenEndpoint, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        ...headers,
      },
      redirect: "follow",
      body: JSON.stringify(formData),
    });
    const body = await response.json();
    if (response.status === 200) {
      //Authentication successful, persist token and update _authStatus
      this._updateToken(body.token);
    } else {
      //Error happened, replace stored token (if any) with null 
      //and update _authStatus
      this._updateToken(null);
      throw new Error(body);
    }
  }

  //This method signs user out by replacing token with null
  unauthenticate() {
    this._updateToken(null);
  }

  //This is needed so components and routes are able to 
  //react to changes in _authStatus
  addStatusListener(listener) {
    this._statusListeners.push(listener);
  }

  //Components need to unsubscribe from changes when they unmount
  removeStatusListener(listener) {
    this._statusListeners = this._statusListeners.filter(
      (cb) => cb !== listener
    );
  }

  _storageKey = "jwt";
  _refreshLeeway = 60;
  _tokenEndpoint = "";
  _refreshEndpoint = "";
  _refreshTimer = undefined;
  //This field holds authentication status
  _authStatus = {
    isAuthenticated: null,
    userId: null,
  };
  _statusListeners = [];

  //This method checks if token refresh is needed, performs refresh 
  //and calls itself again in a second
  async _maybeRefresh() {
    clearTimeout(this._refreshTimer);

    try {
      const decodedToken = decodeToken(this._token);

      if (decodedToken === null) {
        //no token - no need to refresh
        return;
      }

      //Note that in case of JWT expiration date is built-in in token
      //itself, so I do not need to make requests to check expiration
      //Otherwise you might want to store expiration date in _authStatus
      //and localStorage
      if (
        decodedToken.exp * 1000 - new Date().valueOf() >
        this._refreshLeeway * 1000
      ) {
        //Refresh is not needed yet because token will not expire soon
        return;
      }

      if (decodedToken.exp * 1000 <= new Date().valueOf()) {
        //Somehow we have a token that is already expired
        //Possible when user loads app after long absence
        this._updateToken(null);
        throw new Error("Token is expired");
      }

      //If we are not returned from try block earlier, it means 
      //we need to refresh token
      //In my scenario access token itself is used to get new one
      const response = await fetch(this._refreshEndpoint, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        redirect: "follow",
        body: JSON.stringify({ token: this._token }),
      });
      const body = await response.json();
      if (response.status === 401) {
        //Current token is bad, replace it with null and update _authStatus
        this._updateToken(null);
        throw new Error(body);
      } else if (response.status === 200) {
        //Got new token, replace existing one
        this._updateToken(body.token);
      } else {
        //Network error, maybe? I don't care unless its 401 status code
        throw new Error(body);
      }
    } catch (e) {
      console.log("Something is wrong when trying to refresh token", e);
    } finally {
      //Finally block is executed even if try block has return statements
      //That's why I use it to schedule next refresh try
      this._refreshTimer = setTimeout(this._maybeRefresh.bind(this), 1000);
    }
  }

  //This method persist token and updates _authStatus
  _updateToken(token) {
    this._token = token;
    this._saveCurrentToken();

    try {
      const decodedToken = decodeToken(this._token);

      if (decodedToken === null) {
        //No token
        this._authStatus = {
          ...this._authStatus,
          isAuthenticated: false,
          userId: null,
        };
      } else if (decodedToken.exp * 1000 <= new Date().valueOf()) {
        //Token is expired
        this._authStatus = {
          ...this._authStatus,
          isAuthenticated: false,
          userId: null,
        };
      } else {
        //Token is fine
        this._authStatus = {
          ...this._authStatus,
          isAuthenticated: true,
          userId: decodedToken.id,
        };
      }
    } catch (e) {
      //Token is so bad that can not be decoded (malformed)
      this._token = null;
      this._saveCurrentToken();
      this._authStatus = {
        ...this._authStatus,
        isAuthenticated: false,
        userId: null,
      };
      throw e;
    } finally {
      //Notify subscribers that _authStatus is updated
      this._statusListeners.forEach((listener) => listener(this._authStatus));
    }
  }

  //Load previously persisted token (called in constructor)
  _loadToken() {
    this._updateToken(window.localStorage.getItem(this._storageKey));
  }

  //Persist token
  _saveCurrentToken() {
    if (typeof this._token === "string") {
      window.localStorage.setItem(this._storageKey, this._token);
    } else {
      window.localStorage.removeItem(this._storageKey);
    }
  }
}

//Create authProvider instance
const authProvider = new AuthProvider(ENV.auth);

//This hook gives a component a function to authenticate user
export const useAuthenticate = () => {
  const headers = useLanguageHeaders();

  return useCallback(
    async (formData) => {
      await authProvider.authenticate(formData, headers);
    },
    [headers]
  );
};

//This hook gives a function to unauthenticate
export const useUnauthenticate = () => {
  return useCallback(() => authProvider.unauthenticate(), []);
};

//This hook allows components to get authentication status 
//and react to changes
export const useAuthStatus = () => {
  const [authStatus, setAuthStatus] = useState(authProvider.authStatus);

  useEffect(() => {
    authProvider.addStatusListener(setAuthStatus);

    return () => {
      authProvider.removeStatusListener(setAuthStatus);
    };
  }, []);

  return authStatus;
};
Run Code Online (Sandbox Code Playgroud)

功能组件中的这行代码允许知道用户是否经过身份验证: const { isAuthenticated } = useAuthStatus();

  • 在 React Native 文档(https://reactnative.dev/docs/security#secure-storage)中,有一些安全存储解决方案。 (2认同)