I have a problem, I am using Express React with Typescript. My question is about authentication. I would like to send the token to the backend and respond if the user is logged in or not, on every request. Is there a better way than this included in Frontend
, because I need to do the same logic for every private route. I know that I can make checking authentication functions reusable, but I still need to put the logic for rendering pages conditionally. I have one more idea to solve that with wrapper but because of the asynchronous nature of setting states in react I get isAuth false and I am navigated to the "/" every time.
Backend:
export const isAuth = async (req: Request, res: Response) => {
const token = req.get('Authorization')?.split(' ')[1];
if (token) {
try {
const decodedToken = jwt.verify(token, `${process.env.SECRET_KEY}`);
if (decodedToken) res.status(200).json({ isAuth: true });
} catch (e) {
res.status(403).json({ isAuth: false });
}
} else res.status(403).json({ isAuth: false });
};
Frontend
const AddProductPage = () => {
const [isAuth, setIsAuth] = useState<boolean>(false);
useEffect(() => {
const isAuth = async () => {
const response = await fetch(`${config.backendDomain}/auth/is-auth`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' localStorage.getItem('token'),
},
});
const data = await response.json();
setIsAuth(data.isAuth);
};
isAuth();
}, []);
return isAuth ? <AddProduct /> : <Navigate to="/" />;
};
Wrapper approach
interface wrapperProps {
children: React.ReactNode;
}
const IsAuth: React.FC<wrapperProps> = (props) => {
const [isAuth, setIsAuth] = useState(false);
useEffect(() => {
const isAuth = async () => {
const response = await fetch(`${config.backendDomain}/auth/is-auth`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' localStorage.getItem('token'),
},
});
const data = await response.json();
setIsAuth(data.isAuth);
};
isAuth();
}, []);
console.log('ISAUTH', isAuth);
return isAuth ? <div>{props.children}</div> : <Navigate to="/" />;
};
CodePudding user response:
my solution which seemed to me the most reusable was the wrapper idea which I implemented like this, using the Loading state which allowed me to wait for the asynchronous operation. Do not hesitate to share your opinions/ideas. :)
const IsAuth = (props: wrapperProps) => {
const [isAuth, setIsAuth] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
const isAuth = async () => {
const response = await fetch(`${config.backendDomain}/auth/is-auth`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' localStorage.getItem('token'),
},
});
const data = await response.json();
setIsAuth(data.isAuth);
setIsLoading(false);
};
isAuth();
}, []);
useEffect(() => {
setIsLoaded(!isLoading && isAuth);
}, [isAuth, isLoading]);
if (isLoaded) return <div>{props.children}</div>;
if (!isLoading && !isAuth) return <Navigate to="/" />;
return <div>Loading</div>;
};
export default IsAuth;
CodePudding user response:
If you are using Express.js
as your back-end web framework I recommend using express-jwt middleware which checks the Authorization
header on every request and basically replaces your isAuth
function you wrote.
As a side note, you should return a 401 Unauthorized status code and not a 403 Forbidden. express-jwt
will do this for you.
Then, on the front-end, you would perform requests with the token as you did with fetch. But would need to check for 401 errors (and potentially other errors) to decide what to render.
With this approach you would not need to make 2 network calls to the server each time you want communicate, which can cause not only poor performance but also unexpected bugs.