Refresh and Access token issue axios and react native - javascript

// I have the following code
// this is for refreshing the token and it works perfectly fine
export async function refreshTokenGenerator() {
const url = RefreshCurrentTokenURL;
const refreshTokenGeneratedFirst = newUser.getRefreshToken();
const response = await axios
.post(url, {
refreshToken: refreshTokenGeneratedFirst,
})
.catch((error) => {
console.log(
"🚀 ~ file: auth.js:118 ~ refreshTokenGenerator ~ error cant refresh token",
error
);
});
const newRefreshTokenGenerated = response.data.refreshToken;
// assign the new generated refresh token to the user model
// Refresh token loop works fine, no error from overriding the RT
const assignNewRefreshTokenToUserModel = newUser.setRefreshToken(
newRefreshTokenGenerated
);
// access token loops works fine,
// use it whenever u receive error 401 because it means that AT expired
const newAccessTokenGenerated = response.data.token;
return newAccessTokenGenerated;
}
// This part is for authenticating the menu and fetching the categories
async function authenticateMenu() {
const url = CategoriesAuthUrl;
let userToken = newUser.getToken();
const authStr = "Bearer ".concat(userToken);
const options = {
method: "GET",
headers: {
Authorization: authStr,
},
url: url,
};
const response = await axios(options).catch(async (error) => {
if (error.response.status === 401) {
// should call the refreshToken to refresh the access and refresh token
console.log("Error 401 unauthorized");
const newUserToken = await updateAccessToken();
userToken = newUserToken;
}
console.log(
"😡 ~ file: menu.js:28 ~ authenticateMenu ~ Error getting categories from API call",
error
);
});
const fetchedCategories = response.data;
console.log(
"🚀 ~ file: menu.js:40 ~ authenticateMenu ~ fetchedCategories",
fetchedCategories
);
return fetchedCategories;
}
// Get Categories
export async function getCategories() {
return authenticateMenu();
}
**In my HomeScreen I call the fetch categories like this**
useEffect(() => {
async function fetchCatHandler() {
const categoriesFetched = await getCategories().catch((error) => {
console.log(
"🟥 ~ file: HomeScreen.js:63 ~ fetchCatHandler ~ error from fetching categories from Home screen",
error
);
});
setParsedCategories(categoriesFetched);
}
fetchCatHandler();
async function getUserName() {
setUserName(await newUser.getUserName());
}
getUserName();
}, []);
// The code works perfectly fine until the access token is expired. Hence, whenever I receive error.response.status === 401 I call the function updateAccessToken which regenerates new access and refresh token and I save these in the user model
// when I fetch the categories it works fine up until the access token expires and I get the error [AxiosError: Request failed with status code 401].
// Any idea what am I missing/doing wrong??
export async function updateAccessToken() {
console.log("updateAccessToken called");
const newGeneratedTokenAfterExpiration = await refreshTokenGenerator();
newUser.setToken(newGeneratedTokenAfterExpiration);
const userToken = newGeneratedTokenAfterExpiration;
return userToken;
}

Once you've gotten an 401, the response is over. It doesn't change the response if you alter the token afterward. After getting a 401 and generating a new token, you should've sent a new request with the new token and return its response instead.
async function authenticateMenu() {
const url = CategoriesAuthUrl;
let userToken = newUser.getToken();
const authStr = "Bearer ".concat(userToken);
const options = {
method: "GET",
headers: {
Authorization: authStr,
},
url: url,
};
const response = await axios(options).catch(async (error) => {
if (error.response.status === 401) {
// should call the refreshToken to refresh the access and refresh token
console.log("Error 401 unauthorized");
const newUserToken = await updateAccessToken();
userToken = newUserToken;
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// >>>>> This doesn't effect the current req/resposnse. After reseting the token you should send another request, with the new token
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
}
console.log(
"😡 ~ file: menu.js:28 ~ authenticateMenu ~ Error getting categories from API call",
error
);
});
const fetchedCategories = response.data;
console.log(
"🚀 ~ file: menu.js:40 ~ authenticateMenu ~ fetchedCategories",
fetchedCategories
);
return fetchedCategories;
}
Regardless, you shouldn't refresh the token after it has already expired. It's not secure
Best of luck with your project:)

Related

How to improve sequential promises execution and force fulfillment

This code is being used in a Sveltekit web application.
In the first step I get a user jwt token from an api like : dashboard.example.com/auth/local
and in the second step I'm using the response of the first api call to get full information from an api endpoint like this : example.com/api/users/token
This is an endpoint in an Sveltekit application:
import { json as json$1, error } from '#sveltejs/kit';
import axios from 'axios';
import md5 from 'md5';
import { SITE_ADDRESS } from '$lib/Env';
let userToken;
/** #type {import('#sveltejs/kit').RequestHandler} */
export async function POST({ request }) {
const bodyData = await request.json();
let identifier = bodyData.data.identifier;
let password = bodyData.data.password;
let loginToken = bodyData.data.loginToken;
let newLoginToken = md5(identifier + password + process.env.SECURE_HASH_TOKEN);
let dataResult = await axios
.post(`${import.meta.env.VITE_SITE_API}/auth/local`, {
identifier: identifier,
password: password
})
.then((response) => {
return response.data;
})
.then((response) => {
let userSummaryData = response;
userToken = md5(
userSummaryData.user.username + userSummaryData.user.id + process.env.SECURE_HASH_TOKEN
);
let userCompleteData = axios
.post(`${SITE_ADDRESS}/api/users/${userToken}`, {
data: {
userID: userSummaryData.user.id,
username: userSummaryData.user.username
}
})
.then((response) => {
return {
userJWT: userSummaryData.jwt,
userSummary: userSummaryData.user,
userFullSummary: response.data.userFullSummary
};
});
return userCompleteData;
})
.catch((error) => {
// console.log(' ---- Err ----');
});
if (dataResult && newLoginToken == loginToken) {
return json$1(
{
userJWT: dataResult.userJWT,
userSummary: dataResult.userSummary,
userFullSummary: dataResult.userFullSummary
},
{
headers: {
'cache-control': 'private, max-age=0, no-store'
}
}
);
} else if (dataResult && newLoginToken != loginToken) {
throw error(400, 'Something wrong happened');
}
throw error(401, 'Something wrong happened');
}
This code is work perfectly in localhost. But when I test it on host I get error 401.
and the question is :
Why this works on localhost but doesn't work on the server?
How can I improve this kind of promises (I'd like to use the response of the first api call in the second api call and return both
as a result)

React Axios Interceptor - How to use refresh tokens while polling api every 5 seconds

I have a refresh token that needs to be refreshed every 2 minutes. In order to do this I check for a 401 response (using an axios interceptor). If the response from the server is "expired" I retry the api call with a new token I get from making a new POST request to a token endpoint.
const REFRESH_TOKEN_ERRORS = ["token_expired", "invalid_token"];
export const apiHeaders = {
Authorization: `Bearer ${window.localStorage.getItem("jwt")}`,
"Content-Type": "application/json",
};
axios.interceptors.response.use(
function (response) {
return response;
},
async function (error) {
const originalRequest = error.config;
const apiUrl = String(process.env.REACT_APP_API_ROOT);
if (originalRequest.url.includes(apiUrl)) {
// REFRESH_TOKEN_ERRORS contains a list of specific error messages in the API body that should trigger refresh & retry
if (
REFRESH_TOKEN_ERRORS.includes(error.response.data.error_description) &&
!originalRequest._retry
) {
originalRequest._retry = true;
// refresh and set the new headers
const newToken = await getNewRefreshToken();
if (newToken === null) {
return null;
}
axios.defaults.headers.common["Authorization"] = "Bearer " + newToken;
if (originalRequest.config) {
originalRequest.config.headers = apiHeaders;
} else {
originalRequest.headers = apiHeaders;
}
// Retry the original request
return axios(originalRequest);
}
} else {
console.log("Unhandled error", error.response.data);
}
return Promise.reject(error);
}
);
async function getNewRefreshToken() {
// https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#refresh-the-access-token
const tokenUrl = process.env.REACT_APP_OAUTH_ROOT_URL + "/oauth2/token";
const clientId = process.env.REACT_APP_OAUTH_CLIENT_ID;
const refreshToken = window.localStorage.getItem("jwt_refresh_token");
try {
const res = await axios.post(
tokenUrl,
{},
{
headers: {
grant_type: "refresh_token",
client_id: String(clientId),
refresh_token: String(refreshToken),
},
}
);
window.localStorage.setItem("jwt", res.data.access_token);
window.localStorage.setItem("jwt_exp", res.data.exp);
window.localStorage.setItem("jwt_refresh_token", res.data.refresh_token);
return res.data.access_token;
} catch (err) {
console.log("Refresh error", err);
window.localStorage.removeItem("jwt");
window.localStorage.removeItem("jwt_refresh_token");
window.localStorage.removeItem("jwt_exp");
return null;
}
}
This works fine until I try and poll the api at intervals (e.g,. every 5 seconds)
This triggers the axios interceptor and the function is run again (effectively resetting my refresh token every 5 seconds instead of every 2 minutes).
How can I ensure that the refresh token is not reset every 5 seconds/how can I implement polling and refreshing of tokens without the above conflicts?
With my current implementation (whilst polling) I get a "invalid_token" response from the server after 2 minutes and the interceptor does not perform a silent re-request of the refresh token as I would like

Django + React Axios instance header conflict?

I have all my functions based views on django protected with #permission_classes([IsAuthenticated]) so I have to send a JWT as Bearer token on every request.
In the first version I was using this code:
import axios from 'axios';
import { decodeUserJWT } from '../../extras'
const user = JSON.parse(localStorage.getItem("user"));
var decoded = decodeUserJWT(user.access);
var user_id = decoded.user_id
const instance = axios.create({
baseURL: 'http://localhost:8000/api',
headers: {Authorization: 'Bearer ' + user.access},
params: {userAuth: user_id}
});
export default instance;
Everything was working fine.
But then I added interceptors so I could handle the refreshToken process:
const setup = (store) => {
axiosInstance.interceptors.request.use(
(config) => {
const token = TokenService.getLocalAccessToken();
if (token) {
// const uid = await decodeUserJWT(token);
config.headers["Authorization"] = 'Bearer ' + token;
// config.headers["userAuth"] = uid;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
const { dispatch } = store;
axiosInstance.interceptors.response.use(
(res) => {
return res;
},
async (err) => {
const originalConfig = err.config;
if (originalConfig.url !== "/auth/token/obtain/" && err.response) {
console.log("TOKEN INTERCEPTOR");
// Access Token was expired
if (err.response.status === 401 && !originalConfig._retry) {
originalConfig._retry = true;
try {
const rs = await axiosInstance.post("/auth/token/refresh/", {
refresh: TokenService.getLocalRefreshToken(),
});
const { access } = rs.data;
dispatch(refreshToken(access));
TokenService.updateLocalAccessToken(access);
return axiosInstance(originalConfig);
} catch (_error) {
return Promise.reject(_error);
}
}
}
return Promise.reject(err);
}
);
};
What happens?
When I add the line config.headers["userAuth"] = uid; the django server console starts showing up that when the react app tries to access the routes it gets a Not Authorized, and when I take that line off de code ... it works fine.
I also tried to pass the param userAuth in the axios.create and keep only the Bearer config inside the interpector code, but still no positive result, the code with the interpector code only works when I take off the userAuth line from axios.
Any ideia on why this is happening and how can I fix this?

error retrieving user Id token assigned by firebase in the client side

I am using JWT based authentication using firebase Admin SDK in express js.
according to the sign in with custom token when we sign the user with the function signInWithCustomToken(token) firebase sets a user-id token for that user.
according to retrieve id tokens
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// Send token to your backend via HTTPS
// ...
}).catch(function(error) {
// Handle error
});
we can get the token if the user is logged in
but executing this I get error that getIdToken value is null.
i changed the code to
const getUser = async () => {
const token = await firebase.auth().currentUser.getIdToken(/* forceRefresh */true).catch(function(error) {
console.log(error)
});
const userToken = await token;
const getData = async (userToken) => {
const response = await fetch('/getUser', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({idToken: userToke})
})
const data = await response.json()
console.log(responnse)
}
}
getUser();
but still receiving the same error
I looked up for some solutions and found similar answers to the question one of which I implemented was solution
it used onAuthStateChanged method and I am using
<script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-auth.js"></script>
in cdn but now am getting
Uncaught (in promise) TypeError: firebase.auth.onAuthStateChanged is not a function
at profile:40
at new Promise (<anonymous>)
at getIdTokenRefreshed (profile:37)
at profile:50
I changed the above code to this
firebase.initializeApp(firebaseConfig);
const getIdTokenRefreshed = async () => {
return new Promise(async (resolve, reject) => {
const unsubscribe = await firebase
.auth
.onAuthStateChanged(async user => {
unsubscribe()
const refreshedToken = await user
.getIdToken(true)
.catch(err => console.error(err))
resolve(refreshedToken)
console.log(refreshedToken)
}, reject)
});
}
getIdTokenRefreshed();
still getting the second error where onAuthStateChanged is not defined
how do I retrieve the user id token?
UPDATE
const getIdTokenRefreshed = async () => {
try {
const user = firebase.auth().currentUser
if (user) {
const token = await user.getIdToken(true)
console.log(`Token: ${token}`)
return token
} else {
console.log("No user is logged in")
}
} catch (e) {
console.log(`Something went wrong: ${e}`)
}
}
after implementing the above code this is the error
await is only valid in async functions and the top level bodies of modules
First, I'd recommend updating Firebase SDK to latest version which is 8.9.1 at the time of writing this.
<script src="https://www.gstatic.com/firebasejs/8.9.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.9.1/firebase-auth.js"></script>
If you take a look at onAuthStateChanged part in the documentation, it should be:
firebase.auth().onAuthStateChanged(...)
// ^^
// not firebase.auth.onAuthStateChanged
The onAuthStateChanged won't be triggered unless you call the getIdTokenRefreshed function. You can simply refactor that function to:
const getIdTokenRefreshed = async () => {
try {
const user = firebase.auth().currentUser
if (user) {
const token = await user.getIdToken(true)
console.log(`Token: ${token}`)
return token
} else {
console.log("No user is logged in")
}
} catch (e) {
console.log(`Something went wrong: ${e}`)
}
}
Lastly, the variable name is userToken but in request body it is body: JSON.stringify({idToken: userToke}) and you don't need an await before a variable name. Try refactoring the getUser function to:
//const token = await firebase.auth().currentUser.getIdToken(/* forceRefresh */true).catch(function(error) {
// console.log(error)
//});
//const userToken = await token;
const getUser = async () => {
const token = await firebase.auth().currentUser.getIdToken(true)
const response = await fetch('/getUser', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({idToken: token})
})
const data = await response.json()
console.log(data)
return data
}
getUser().then(data => {
console.log("Data received")
})

Handling query in React and Express

Somewhere in my React application I used REST API to send request to the server. In my URL I want to use query (in the postIconsTransition method), but when I send a request to the server, server tells me could not found this URL (I build this error in my server). If I use this URL without any query the request in the postIconsTransition method works fine. postId and authContext.userId work fine, can anyone tell me what's wrong with my code?
In my component where I send request:
const likeHandler = async () => {
setLike(prevState => !prevState);
if (!like) {
try {
await postIconsTransition(props.postId, "inc");
} catch (error) {}
} else {
try {
await postIconsTransition(props.postId, "dec");
} catch (error) {}
}
};
In useHttp.js component:
const postIconsTransition = async (postId, addtionAddress) => {
return await transitionData(
`http://localhost:5000/post/${postId}/${authContext.userId}?t=${addtionAddress}`,
"POST",
null,
{ Authorization: `Bearer ${authContext.token}` }
);
};
transitionData method:
const transitionData = useCallback(
async (url, method = "GET", body = null, headers = {}) => {
setIsLoading(true);
const abortController = new AbortController();
activeHttpRequest.current.push(abortController);
try {
const response = await fetch(url, {
method,
body,
headers,
signal: abortController.signal
});
const responseData = await response.json();
activeHttpRequest.current = activeHttpRequest.current.filter(
reqCtrl => reqCtrl !== abortController
);
if (!response.ok) {
throw new Error(responseData.message);
}
setIsLoading(false);
return responseData;
} catch (error) {
modalContext.err(error);
setIsLoading(false);
throw error;
}
},
[modalContext.err]
);
In Express:
router.post(
"/:postId/:userId?t=inc",
tokenChecker,
postController.updateLikesComments
);
router.post(
"/:postId/:userId?t=dec",
tokenChecker,
postController.updateLikesComments
);
All of them work fine but when I use query in my URL, it's not working any more.
You don't specify query parameters in express routes like that. Just send them. Express can read it.
router.post(
"/:postId/:userId",
tokenChecker,
postController.updateLikesComments
);
// Notice that you don't need the other one.
and in your controller check the parameter
// controller's code
const t = req.query.t;
if (t === 'inc') {
// do what you want here
}
if (t === 'dec') {
// do what you want here
}

Categories

Resources