I am trying to make a login system using a typescript/react frontend and a typescript/nodejs backend. Currently, when the user is logged out, my backend returns a 401 (not authorized) error code, since the user isn't logged in. My issue is that this error code gets automatically logged in the console without me telling it to do so.
So, in the frontend I made a HttpService, which I am using:
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, CancelTokenSource } from "axios";
import { useNavigate } from "react-router-dom";
type CustomHeaders = {
[key: string]: string | number | boolean;
};
type RequestResult<T> = {
request: Promise<AxiosResponse<T>>;
cancel: () => void;
};
class HttpService {
private baseUrl: string;
private instance: AxiosInstance;
constructor(baseURL: string = "http://localhost:3001") {
this.baseUrl = baseURL;
this.instance = axios.create({ baseURL: this.baseUrl, withCredentials: true });
}
private get defaultHeaders(): CustomHeaders {
return {
Authorization: localStorage.getItem("Authorization") || "",
"Content-Type": "application/json",
};
}
private async request<T>(
method: "get" | "post" | "put" | "delete",
url: string,
data: any = null,
customHeaders: CustomHeaders = {}
): Promise<RequestResult<T>> {
const headers = { ...this.defaultHeaders, ...customHeaders };
const source: CancelTokenSource = axios.CancelToken.source();
const config: AxiosRequestConfig = {
method,
url,
headers,
cancelToken: source.token,
};
if (data) {
config.data = data;
}
return {
request: this.instance.request<T>(config),
cancel: source.cancel,
};
}
public get<T>(url: string, customHeaders: CustomHeaders = {}): Promise<RequestResult<T>> {
return this.request<T>("get", url, null, customHeaders);
}
public post<T>(url: string, data: any, customHeaders: CustomHeaders = {}): Promise<RequestResult<T>> {
return this.request<T>("post", url, data, customHeaders);
}
public put<T>(url: string, data: any, customHeaders: CustomHeaders = {}): Promise<RequestResult<T>> {
return this.request<T>("put", url, data, customHeaders);
}
public delete<T>(url: string, customHeaders: CustomHeaders = {}): Promise<RequestResult<T>> {
return this.request<T>("delete", url, null, customHeaders);
}
}
export default HttpService;
Before I get to the next part of the code, I want to explain that this line, which is from the code above, is where the unwanted error log happens:
request: this.instance.request<T>(config),
And this is the code snippet that uses the httpService to check if the user is logged in:
useEffect(() => {
const checkUserLoggedIn = async () => {
try {
const { request } = await httpService.get("/api/v1/user/checkAuth");
const res: any = await request;
setUser(res.data.user);
} catch (err: any) {
setUser(null);
} finally {
setIsLoading(false);
}
};
checkUserLoggedIn();
}, []);
Now the important part from the backend:
const authMiddleware = async (req: Request, res: Response, next: NextFunction) => {
const refreshToken = req.cookies.refresh_token;
const accessToken = req.cookies.access_token;
if (!refreshToken && !accessToken) {
clearTokenCookies(res);
return res.status(401).json({ message: "Access denied. No token provided." });
}
//more validation would be after this
}
This is a middleware I already implemented for some other functions to check if the user is allowed to send a request, I just used it for the checkAuth endpoint as well.
So, this is the exact log in the console in the frontend that I do not want:
HttpService.ts:51
GET http://localhost:3001/api/v1/user/checkAuth 401 (Unauthorized)
dispatchXhrRequest @ xhr.js:195
xhr @ xhr.js:15
dispatchRequest @ dispatchRequest.js:51
_request @ Axios.js:173
request @ Axios.js:40
wrap @ bind.js:5
request @ HttpService.ts:51
get @ HttpService.ts:57
checkUserLoggedIn @ AuthProvider.tsx:28
(anonymous) @ AuthProvider.tsx:38
commitHookEffectListMount @ react-dom.development.js:23189
commitPassiveMountOnFiber @ react-dom.development.js:24965
commitPassiveMountEffects_complete @ react-dom.development.js:24930
commitPassiveMountEffects_begin @ react-dom.development.js:24917
commitPassiveMountEffects @ react-dom.development.js:24905
flushPassiveEffectsImpl @ react-dom.development.js:27078
flushPassiveEffects @ react-dom.development.js:27023
(anonymous) @ react-dom.development.js:26808
workLoop @ scheduler.development.js:266
flushWork @ scheduler.development.js:239
performWorkUntilDeadline @ scheduler.development.js:533
So, I have been trying to find solutions for a few hours now, but I got nothing, however I have tried some different things. First was adding an axios interceptor, however that did not fix the auto logging issue, same with a try/catch block. I have slowly been able to gather that this is a default XHR behavior, and may even be something you do not want to "fix"? But I am not sure. One way I found to at least remove the error was adding to my constructor:
XMLHttpRequest.prototype.open = function () {
this.addEventListener("error", function (event) {
event.preventDefault();
console.log("error", event);
});
this.addEventListener("load", function (event) {
console.log("load", event);
});
};
However, this also did not properly work. (and would probably break some kind of other code)
So now, I am stumped. I do not know how to solve this, or even IF I should solve this? Should I just send my errors differently? Like for example just sending res.status(200).json({loggedIn: false})
or something like that?
Has anyone ever dealt with this before? If so, I would be very thankful for your response! I will of course update my question if I have missed anything or not specified anything well enough.
Edit: I also found this in my research before this question, but it was answered by you as well. This behavior is not something that you want to alter. However, I still feel bad when these errors log in useless cases like this. It doesn't feel right that an error is logged when the user isn't logged in, since parts of the application should still be accessible when logged out. So, now I am updating this question to ask if a solution like this would be ok: (I saw this somewhere else) Send a 200 code, but make some kind of solution that tells the frontend if the user is logged in or not. Like a boolean saying true if the user is logged in, or false if the user is logged out.
Another specification: I already have code for refreshing tokens. My issue here is the case where the user is not logged in at all. Like no access and refresh token specified.