Error "invalid_grant" after using msal token from msal-react - reactjs

I need your help with an unreachable error when msal token expire, specifically an invalid_grant error. I have a exception but this error only is showing on console. Thanks you
const refreshToken = async () => {
const request = {
account: account,
scopes: ["https://graph.microsoft.com/User.Read"],
};
try {
const response = await instance.acquireTokenSilent(request);
localStorage.setItem("accessToken", response.accessToken);
setToken(response.accessToken);
} catch (e) {
console.log(e);
const isServerError = e instanceof ServerError;
const isInteractionRequiredError = e instanceof InteractionRequiredAuthError;
const isInvalidGrantError = (e.errorCode === "invalid_grant");
if (isInteractionRequiredError) {
try {
const response = await instance.acquireTokenRedirect(request);
localStorage.setItem("accessToken", response.accessToken);
setToken(response.accessToken);
return;
} catch (e) {
console.log("InteractionRequiredAuthError:" + e);
handleError(e);
}
}
if (isServerError && isInvalidGrantError && !isInteractionRequiredError) {
localStorage.clear();
window.location.reload();
}
}
};
I tried to catch that error but it was not possible

Related

How to refresh firebase access token

In the current project, I log in to Firebase and get the user's information.
However, the log below occurs on the server, and there is a problem of getting all user information, not a specific user.
{"level":30,"time":1675750089706,"pid":16748,"hostname":"DESKTOP-JP9RKDH","msg":"HTTP GET: /api/friends/"}
{"level":30,"time":1675750089707,"pid":16748,"hostname":"DESKTOP-JP9RKDH","msg":"UserID is invalid, retrieving all friends"}
{"level":30,"time":1675750089733,"pid":16748,"hostname":"DESKTOP-JP9RKDH","msg":"Decoded Token User ID: Yk1eA8Vbh7fFIRd3eTNXvyHCdwH3"}
I thought there was no problem because I was refreshing when the token expired as follows.
Also, checking the token stored in the cookie every hour showed that it was a new token.
Please let me know what is causing this error.
const setToken = token => {
cookie.set('FB_TOKEN', token);
};
export const getToken = () => {
fbAuth.onIdTokenChanged(async user => {
if (user) {
const newToken = await user.getIdToken();
setToken(newToken);
}
});
const token = cookie.get('FB_TOKEN') ?? '';
return token;
};
export const login = createAsyncThunk('user/login', async (data, { rejectWithValue }) => {
try {
let credential;
if (data.type === 'google') {
localStorage.clear();
const provider = new GoogleAuthProvider();
credential = await signInWithPopup(fbAuth, provider);
const token = await credential.user.getIdToken();
setToken(token);
} else {
credential = await signInWithEmailAndPassword(fbAuth, data.loginInfo.email, data.loginInfo.password);
const token = await credential.user.getIdToken();
setToken(token);
}
return {
id: credential.user.uid,
nickname: credential.user.displayName,
email: credential.user.email,
image: credential.user.photoURL,
};
} catch (error) {
return rejectWithValue(error.response.data);
}
});
axios.defaults.baseURL = backendUrl;
axios.defaults.withCredentials = true;
axios.defaults.headers.post['Content-Type'] = 'application/json';
axios.interceptors.request.use(
async config => {
const token = await getToken();
config.headers.Authorization = `Bearer ${token}`;
return config;
},
error => {
return Promise.reject(error);
},
);
export const loadMyFriends = createAsyncThunk('schedule/loadMyFriends', async () => {
const response = await axios.get('/friends');
return response.data;
});

How to stop React from finishing render when axios.interceptors.response handles the error?

I am working on a react app and I use tokens and refresh tokens for authentication. Whenever the backend returns a 401, the axios.interceptors.response picks it up and tries to refresh my token. If it succeeds, it will reinitiate the original call with the updated headers. See the code below:
// To avoid infinite loops on 401 responses
let refresh = false;
axios.interceptors.response.use(
(resp) => resp,
async (error) => {
if (error.response.status === 401 && !refresh) {
refresh = true;
const response = await axios.post(
"/api/auth/refresh",
{},
{ withCredentials: true }
);
if (response.status === 200) {
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${response.data["accessToken"]}`;
return axios(error.config);
}
}
refresh = false;
return error.response;
}
);
This by itself works great, but not in combination with the code below in one of my components:
const [pages, setPages] = useState();
const [error, setError] = useState();
const navigate = useNavigate();
useEffect(() => {
async function fetchInfo() {
const response = await getMyPages();
if (response.status === 200) {
setPages(response.data);
}
else if (response.status === 401) {
setError(t("error.notAuthorized"));
navigate(`/login`, { replace: true });
}
// Any other error
else {
setError(t("error.unexpected"));
}
}
fetchInfo();
}, [t, navigate]);
// getMyPages function
export async function getMyPages() {
try {
const result = await axios.get(`/api/user/mypages`);
return result;
} catch (err) {
return err.response;
}
}
The problem is that the user is navigated to /login before the new request (with refreshed token) is made and finished. So when the new request finishes, I am not in the original component anymore and I can no longer update the pages state.
Any suggestions on how to handle this?
useEffect(() => {
let isMounted = true;
const controller = new AbortController();
const getMyPages = async () => {
try {
const response = await axios.get(`/api/user/mypages`, {
signal: controller.signal
});
isMounted && setPages(response.data);
} catch (err) {
navigate(`/login`, { replace: true });
}
}
getMyPages();
return () => {
isMounted = false;
controller.abort();
}
}, [])

Axios Retry Infinite Loop

I am using an Axios interceptor (in React) to retry on 401 (when my access token expires). I want to limit to one retry but for some reason I'm unable to read the retried property I am defining.
This is the code I am using in the interceptor.
const responseIntercept = axios.interceptors.response.use(
(response) => response,
async (error) => {
const prevRequest = error?.config;
console.log(prevRequest);
console.log(prevRequest.retried);
if (error?.response?.status === 401 && !prevRequest?.retried) {
await new Promise(r => setTimeout(r, 1500)); // only here to delay the infinite retries
prevRequest.retried = true;
// log here returns true
const newAccessToken = await refresh();
prevRequest.headers['Authorization'] = newAccessToken;
return axios(prevRequest);
}
return Promise.reject(error);
}
);
For some reason, logging of prevRequest shows an object with the property retried, but the second log of .retried always logs 'undefined'. I assume this is the problem but I have no idea why I can see the property set but can't access it.
If I log prevRequest after adding the property, it does return true.
console log
Edit (solution): After taking bogdanoff's advice, this is the working solution I ended up with:
const NO_RETRY_HEADER = 'x-no-retry'
...
const responseIntercept = axiosPrivate.interceptors.response.use(
(response) => response,
async (error) => {
var prevRequest = error?.config;
if (error?.response?.status === 401 && prevRequest?.headers[NO_RETRY_HEADER] == null) {
// get new token, return error if refresh errors
try {
const newAccessToken = await refresh(controller.signal);
// retry with new token
prevRequest.headers[NO_RETRY_HEADER] = 'true';
prevRequest.headers['Authorization'] = newAccessToken;
return axiosPrivate(prevRequest);
} catch (error) {/* no-op */}
}
return Promise.reject(error);
}
);
I have been there recently, I used headers instead of modifying config.
const NO_RETRY_HEADER = 'x-no-retry'
const responseIntercept = axios.interceptors.response.use(undefined, async (error) => {
if (!axios.isCancel(error) && axios.isAxiosError(error) && error.response.status === 401) {
if (error.config.headers && error.config.headers[NO_RETRY_HEADER]) {
return Promise.reject(error)
}
error.config.headers ||= {}
error.config.headers[NO_RETRY_HEADER] = 'true' // string val only
const newAccessToken = await refresh()
error.config.headers['Authorization'] = newAccessToken
return axios(error.config)
}
return Promise.reject(error)
})

How to use async/await instead of promises here

const Video = require("");
const token = "";
const connectOptions = {logLevel: "off"}
 
const startRoom = function(token) {
 console.log("hello world");
 Video.connect(a)
   .then(room => null
   })
   .catch(error => {
     console.log("error");
     return error
   });
}
The async/await will lead to removal of catch. Which is what I want to achieve.
Just fyi, you're not using await INSTEAD of promises, you're using await WITH promises. async functions return promises, and await waits for promises to resolve
const Video = require("twilio-video");
const token = "test_token";
const connectOptions = {video: false, audio: false, logLevel: "off"}
const startRoom = async function(token) {
console.log("hello world");
try {
const room = await Video.connect(token, connectOptions)
console.log("got a room");
} catch(error) {
console.log("error");
}
}
Just wrap it in try-catch
const getRoom = async () => {
try {
const room = await Video.connect(token, connectOptions);
console.log("got a room");
} catch (e) {
console.log("error");
}
}
or you can use this also..
async function startRoom(token) {
console.log("hello world");
try {
let response = await fetch(token);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
let room = await video.connect(token, connectOptions);
console.log("got a room");
}
} catch (e) {
console.log(e);
}
}

JWT gives invalid token

Working locally, my jwt token is invalid but in jwt.io it shows verified signature. Not sure what i am missing. I am having invalid signature whenever i tried to make a call to a api whithin the app.
Link.js
const { Router } = require("express");
const Link = require("../models/Link");
const auth = require("../middleware/auth.middleware");
const router = Router();
router.get("/", auth, async (req, res) => {
try {
const links = await Link.find({ owner: req.user.userId });
res.json(links);
} catch (error) {
res.status(500).json({ message: "Something went wrong, try again" });
}
});
auth.middleware.js
const jwt = require("jsonwebtoken");
const config = require("config");
module.exports = (req, res, next) => {
if (req.method === "OPTIONS") {
return next();
}
try {
const token = req.headers.authorization; // Token
if (!token) {
return res.status(401).json({ message: "No Authorization" });
}
const decoded = jwt.verify(token, config.get("secret"));
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ message: "No Authorization" });
}
};
Links.tsx
const LinksPage: React.FC = () => {
const [links, setLinks] = useState([]);
const fetchLinks = useCallback(async () => {
try {
const fetched = await request("http://localhost:5000/api/link/", "GET", null, {
Authorization: Token,
});
setLinks(fetched);
} catch (error) {
alert(error);
}
}, []);
};
Maybe the "req.headers.authorization" was not what you looking for.
Try to console.log(req.headers.authorization) F12 in chrome, firefox.
I suggest you also POSTMAN (free software). It help me a lot for debugging the back end (server side).
I solved the problem. I had to json.parse(token) which stored in the client in order to jwt.verify(token, secret), but instead i was verifying string that contains object of token and userId.

Resources