[英]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),但这是您需要遵循的一般步骤:
setTimeout
in order to refreshsetTimeout
来刷新 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.