简体   繁体   English

需要帮助尝试在本机反应中刷新我的令牌

[英]Need help trying to refresh my token in react native

I thought it was a simple task, storing my token, setting a timer and fetching the token whenever the timer expired, i was so wrong, after watching and reading several articles to how to approach this, i'm super lost right now, i need help on both storing my token (or both, username data plus token? not sure anymore), and refreshing the token whenever expires.我认为这是一项简单的任务,存储我的令牌,设置一个计时器并在计时器到期时获取令牌,我错了,在看了几篇关于如何解决这个问题的文章之后,我现在超级迷路,我在存储我的令牌(或同时存储我的令牌(或同时存储用户名数据和令牌?不再确定)以及在令牌过期时刷新令牌方面需要帮助。

Yes i've seen quite a few questions related to this on stack overflow, but many of these are related to specific issues and not how to do it.是的,我在堆栈溢出中看到了很多与此相关的问题,但其中许多与特定问题有关,而不是如何去做。

my app connects to office 365 via microsoft graph from an api (net core 2.0).我的应用程序通过 microsoft graph 从 api (net core 2.0) 连接到 office 365。

on my app i got this code to fetch the data from the api passing the parameters my username and password在我的应用程序上,我得到了这个代码来从 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;
  }

the result is this.结果是这样的。

{"Username":"sensitive data","DisplayName":"sensitive data","GivenName":"sensitive data","SurName":"sensitive data","Email":"sensitive data","Token":"ZSI6Im42aGRfdDVGRHhrSzBEVklJUXpxV09DWHZ4dWc0RlhWVkI4ZVJ6dEFsWDAiLCJhbGciOiJSUzI1NiIsIng1dCI6IlNzWnNCTmhaY0YzUTlTNHRycFFCVEJ5TlJSSSIsImtpZCI6IlNzWnNCTmhaYm5ldC8zOTBmODU5NS1kZTFlLTRmNmQtYTk1NC0yNWY2N5MjkwMTYsImV4cCI6MTU5MjkzMjkxNiButVBqe3E3QwcBr1P0G_dWyC9ASQU0psGDPnsQPHp0T070ROZ_mcPitgquNfsO5JZ8-o056l_aePhXSMO7bHWmUBbVn7TA1UoYIz3lAoOzvE6juadve4aU3goeaBj8PIrhG0M2zEEfKgOL1Al9MSU1GGUmRW9dBofeA4e1cGmlGQrUKnt73n0sHap6","PhotoBase64":null}

this is pretty much all i got, currently, i've used async storage on this app, but only to store an object with "useless" data to say the least, i'm not sure if async storage is the way to go with this or not, if not, what can i do?这几乎是我得到的全部,目前,我在这个应用程序上使用了异步存储,但至少可以存储带有“无用”数据的 object,我不确定异步存储是否是通往 go 的方式这与否,如果不是,我该怎么办?

EDIT: after reading some more, i discovered that i need to ask for a second token, the refresh token from microsoft graph https://massivescale.com/microsoft-v2-endpoint-primer/ still need help on how to store the data and refresh the token whenever expires,编辑:阅读更多内容后,我发现我需要第二个令牌,来自 microsoft graph https://massivescale.com/microsoft-v2-endpoint-primer/的刷新令牌仍然需要有关如何存储数据的帮助并在令牌到期时刷新令牌,

EDIT 2: unfortunately i'm not getting neither the refresh token or the expiresAt value from the api编辑 2:不幸的是,我没有从 api 获得刷新令牌或 expiresAt 值

I can not help with that specific authentication provider (never worked with office 365) but this is general steps that you need to follow:我无法帮助该特定身份验证提供程序(从未使用过 Office 365),但这是您需要遵循的一般步骤:

  1. Send request(s) to get access and refresh tokens发送请求以获取访问和刷新令牌
  2. Store tokens in a storage that persist data through reloads/restarts (for web it would be localStorage, for RN sqlite or asyncstorage or whatever do you use)将令牌存储在通过重新加载/重新启动保留数据的存储中(对于 web,它将是 localStorage,对于 RN sqlite 或 asyncstorage 或您使用的任何东西)
  3. Save token and authentication state that it's available for all your components (Redux, Context API or even your own solution).保存令牌和身份验证 state,它可用于您的所有组件(Redux、上下文 API 甚至您自己的解决方案)。 This is needed to show/hide parts of application when user authenticates, unauthenticates or token is expired当用户进行身份验证、取消身份验证或令牌过期时,这是显示/隐藏应用程序部分所必需的
  4. You need to know somehow when token will be expired (can't say how to do it but API docs should have some info) and use setTimeout in order to refresh您需要以某种方式知道令牌何时过期(不能说如何做,但 API 文档应该有一些信息)并使用setTimeout来刷新
  5. When you refreshed token, you should persist it (see n.2) and update global auth state (see n.3)当您刷新令牌时,您应该保留它(参见 n.2)并更新全局身份验证 state(参见 n.3)
  6. When app just (re)started, check if you have access/refresh tokens persisted in storage (see n.2) and update global auth state accordingly (see n.3)当应用程序刚刚(重新)启动时,检查您是否在存储中保留了访问/刷新令牌(请参阅 n.2)并相应地更新全局身份验证 state(请参阅 n.3)
  7. You routes should react to auth state changes (see docs to your routing library, something about protected/authenticated routes).您的路由应该对 auth state 更改做出反应(请参阅路由库的文档,有关受保护/经过身份验证的路由)。 Your components that display sensitive content also should react to auth state changes.显示敏感内容的组件也应该对 auth state 更改做出反应。

Here is my auth solution for Reactjs (do not have RN example, unfortunately) that authenticates client against my own API using JWT.这是我的 Reactjs 的身份验证解决方案(不幸的是,没有 RN 示例),它使用 JWT 针对我自己的 API 对客户端进行身份验证。 Access token in this scenario is refresh token as well.这种情况下的访问令牌也是刷新令牌。 I use an approach without Redux, just pure React and JS.我使用没有 Redux 的方法,只是纯 React 和 JS。 I hope this would help you.我希望这会对你有所帮助。

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

This line of code inside of functional component allows to know if user is authenticated or not: const { isAuthenticated } = useAuthStatus();功能组件内的这行代码允许知道用户是否经过身份验证: const { isAuthenticated } = useAuthStatus();

I'm not familiar with that api, but I can tell you, in general terms, how I do it in my react native app.我不熟悉那个 api,但我可以告诉你,一般来说,我是如何在我的 react native 应用程序中做到这一点的。

First of all, as you said, you need both the access token and the refresh token.首先,正如您所说,您需要访问令牌和刷新令牌。 From the documentation:从文档中:

Refresh Tokens are only returned when you include offline_access in your first scopes list.仅当您在第一个范围列表中包含offline_access时,才会返回刷新令牌。

So basically you need to add the offline_access scope in your authentication request and then the response should include a refresh token token prop:所以基本上你需要在你的身份验证请求中添加offline_access scope 然后响应应该包括一个刷新令牌令牌道具:

ApiLogin({
    ...otherProps,
    scope: "my other scopes offline_access"
})

Once you get the response you should store the tokens data.收到响应后,您应该存储令牌数据。 I's not safe to do it with async storage.使用异步存储来做这件事是不安全的。 I would advise you to store that information in a secure way.我建议您以安全的方式存储该信息。 There are some libraries to do it.一些图书馆可以做到这一点。

After that you can keep track of the expiration date in the client and then perform the refresh token request, as you said.之后,您可以跟踪客户端中的到期日期,然后执行刷新令牌请求,如您所说。 You can also keep doing requests as usual and when the api returns a 401 error response (unauthorized, happens on token expiration) you can try to get a new token with the refresh token (if it fails it probably means the refresh token has also expired and you can redirect the user to the login screen).您也可以像往常一样继续执行请求,当 api 返回 401 错误响应(未经授权,在令牌到期时发生)时,您可以尝试使用刷新令牌获取新令牌(如果失败可能意味着刷新令牌也已过期并且您可以将用户重定向到登录屏幕)。 From the documentation you provided:从您提供的文档中:

To exorcise your Refresh Token, we need to make another HTTP POST back to the provider.为了驱除您的刷新令牌,我们需要将另一个 HTTP POST 回送给提供者。 The POST's body must be encoded as application/x-www-form-urlencoded . POST 的正文必须编码为application/x-www-form-urlencoded This body will be POSTed up to https://login.microsoftonline.com/common/oauth2/v2.0/token .此正文将发布到https://login.microsoftonline.com/common/oauth2/v2.0/token The prototype for this call should look like:此调用的原型应如下所示:

 https://login.microsoftonline.com/common/oauth2/v2.0/token Content-Type: application/x-www-form-urlencoded grant_type=refresh_token& refresh_token=[REFRESH TOKEN]& client_id=[APPLICATION ID]& client_secret=[PASSWORD]& scope=[SCOPE]& redirect_uri=[REDIRECT URI]

With that you should be able to get a new access token that you should use until it expires and then repeat the process.有了它,您应该能够获得一个新的访问令牌,您应该使用它直到它过期,然后重复该过程。

Let me know if my response was helpful or any doubts you might have.让我知道我的回答是否有帮助或您可能有任何疑问。 Again, I'm not familiar with the api and the things I posted here are only my interpretation of the docs, so they might need some adaptations.同样,我对 api 并不熟悉,我在这里发布的内容只是我对文档的解释,因此它们可能需要一些调整。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 我需要帮助尝试在 React 游戏中连接我的不同组件 - I need help trying to connect my different components in a React game 需要帮助弄清楚我的数据操作/道具传递 React-Native/react-native-calendars - Need help figuring out my data manipulation/prop passing React-Native/react-native-calendars 使用 redux saga 在 react native 中实现令牌刷新 - Implement token refresh in react native with redux saga 刷新并访问令牌问题 axios 并做出本机反应 - Refresh and Access token issue axios and react native 在本机反应中使用 goBack() 需要帮助 - Need help using goBack() in react native 需要帮助理解 react-native 语法 - Need help understanding react-native syntax 我正在尝试在我的反应应用程序中添加图标,但此方法不起作用需要帮助和指导 - I am trying to add icons in my react app but this method not working Need help and guidance 当应用程序更新时react-native-fcm 令牌刷新 - react-native-fcm token refresh when app is updated 当 jwt 刷新令牌未过期时,React Native 应用程序注销 - React Native app logs out when jwt refresh token is not expired React native Refresh有效,但下一次调用仍使用最后一个令牌 - React native Refresh works but next call still uses the last token
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM