I just finished my session (cookie) based login system in my React+Redux app.
Then I realized that there is no way how I can check if the user logged out from another location (different chrome tab, removing the cookie, server session invalidation, expiration).
I was looking on Instagram's website what using React too. It seems if you log out in a different browser tab, you can still route to another place in-app until u hit some API fetching... Then website is automatically refreshed...
BUT, there is also some kind of system what realize that user is unlogged even when I do some actions whatnot require API calls.
So how to realize these situations in the best way? How do you handle them when developing.
I'm not exactly sure how Instagrams Authentication works but I'd imagine that it's handled by middleware and when you request an API call, it will check to see if the user has an Auth Token stored in Cookies or whatever before initiating the API Request.
You can easily do this yourself by adding a Redux middleware that checks to see if the cookie is there before dispatching the next action. If it's not there you can return an error message to the user or redirect them or even dispatch a redux action that clears out all loaded data and then finally redirect them back to the login page.
The reason why Instagram is only locking the user when it hits an API call is that you can't really do anything dangerous to the users account if the cookie was deleted as you can't make changes to their account (commenting, posting, changing account settings etc.) without interacting with the API. Therefore, the middleware doesn't have to run every time an action has been dispatched which technically makes their application more performant.
Example Redux middleware
import Cookies from 'js-cookie';
const clientHasToken = store => next => action => {
const authToken = Cookies.get('auth');
if (!authToken) {
// redirects user, but you could do anything here
return window.location.href = '/login';
}
// if user has an auth token, proceed to the next action
next(action);
};
export default clientHasToken;
Related
I am working on a dashboard where when user login, I make a request to an API to confirm the log in details and then I save the response to localStorage(to be able to persist users) and at the same time I have an AuthContext which I also set to the response. After this is done, the user sees the dashboard main page first and the data here is gotten from the useContext Api, instead of me making another api call ....my problem is this works fine when user logs in, but when user reloads, the app breaks because it sees the context value as null...which is the default......Also, if I try getting the data directly from localStorage, it works fine
If a user logs in with user and password, he gets an new api key,
so react can access the Rest Api with his user account. How do you save this api key? And what happend if the user clicks 'refresh page'?
Of course I can initalize the Rest App every time with
<script>
window.REP_LOG_API_KEY = '19e8317a38b24af82da056f6ed36e831ea6b8f9bfcad996aaa56ec773f9f2e1d';
</script>
<script src="build/reactapp.js"></script>
but dont look very secure (but I like the idea of changing this key
every page request, if you have no single page application and react
is only used here and there).
To store the Api Key in a cookie would be also possible (secure but not
httponly, normally I only use safe cookies). Is this the only way?
I'm still not quite sure how to use react with a rest api
with individual api keys. Thany you.
The API key you are talking about is probably cookies/authentication token. If it is cookies, you need to enable httpOnly to prevent attacks. For authentication token, the most common way to store is in localStorage or sessionStorage. However, it is insecure, even with HTTPS and short expiry dates (and you do HAVE to use them). Putting it in Redux store is the same as putting it in a global js object where everyone can see.
What will protect your app is to check standard headers to verify the request is same origin (source and target origins check) and CSRF token. Also a common pattern is to verify the token signature before storing and using it. You can check out Auth0 blog on where to store it here: https://auth0.com/docs/security/store-tokens
There are several ways to do this. If you are using a state management library like Redux, MobX, Flux etc, then you can put it there.
Another place to store them is in the browser local storage. This will keep the token saved even if the user refreshes the page or open a new tab etc. Yet I am not 100% sure whether it's a safe thing to do.
Or you can attach it to the Rest Client itself. IMO, this is the best way to do it. I will summarize the steps to do that in brief.
Create a Rest Client by wrapping a solution like fetch-api or axios.
Add a module state, where you can store any data
Whenever making a call, check if there is a token, if a token is not there in the state, then authenticate first.
If the token is there, use it to make the api call. If the request fails with a 403(or may be 401. It depends) error, that means the token has possibly expired. So authenticate again and update the token.
Something like this,
class ApiClient {
constructor() {
this.token = null;
}
login(username, password) {
// make the call to login
// set this.token with the response
}
request() {
// Make the API call using the token
}
}
What will happen with a refresh? Then since the token is not there, the authentication will need to happen again. This will not be a problem if you use cookies to manage sessions.
You can put it in Redux store. I think it is the best implementation.
I’m trying to figure out a React app using JWT for authentication, I dont’t really know how to plan it, for example:
Step 1: The user successfully logs in the app, gets a JWT token that is saved on localStorage.
Step 2: As soon as the user is logged in, the route changes and a request to the REST API is made, the request is authenticated using the token previously saved. The fetched data is now on state.
Step 3: The app has other routes that actually just filters the previously fetched data, so I think making new requests just to check auth would just makes things slower for no reason.
I would like to know a good practice to handle that, maybe check auth after a certain amount of time. Or the right thing to do is make requests on every route change just to check if the user is still authenticated?
The app has other routes that actually just filters the previously fetched data
make requests on every route change just to check if the user is still authenticated
If the user already has the data, it makes no sense from the security point of view to re-authenticate for the same data. Only re-fetch if you need to make sure the data is updated when the route changes.
I'm using the following combination of packages:
react
redux
react-cognito
react-router (v4)
redux-saga
(and I'll disclaim that I'm pretty new with all of these)
Currently I have a PrivateRoute component which will check (and refresh if necessary) an expired IdToken on route changes. This works well.
My problem is if the browser is open past token expiry to a PrivateRoute which is polling my API and sending along the IdToken in its 'Authorization' header. The API will start returning 401.
Options I've Thought of:
Act on API Error
I could catch the 401 error and dispatch an action to refresh the token, but
if there is any issue refreshing the token I wind up in an infinite loop hammering AWS, so need some logic to catch and prevent this. Perhaps redirect to login route if refresh fails?
I then need to add complexity to all my private API calls throughout my app to have this logic, and re-do the requested API call upon successful refresh.
Pre-empt API Error
To me it makes more sense to separate API calls and keeping the auth token valid. Considering react-cognito stores the token expiry time in cognito.user.signInUserSession.idToken.payload.exp, maybe it is possible to pre-empt the API call and expiry.
How to best do this though? At login a refresh action could be 'scheduled' using setTimeout for (currentTime - expiryTime - someBuffer) seconds in the future.
I'm assuming (haven't verified) AWS will let you refresh an IdToken before it expires. I don't want to wait until afterwards else some API calls may have already failed.
are there concerns with using setTimeout with a timeout that may be up to 1 hour long?
Alternatively I could set something up to poll cognito.user.signInUserSession.idToken.payload.exp frequently to detect and refresh an almost-expired token?
Any suggestions?
So pretty much I made a login page communicates with a backend to login a user. Now I want to take their data and store it for all my other components to know that there is
A user logged in
Have access to the user's information
I figured based on past StackOverflow answers, that the best way to do this is using JWT and LocalStorage. But all the answer's I've encountered seem to use Redux. I'm not using Redux at all for my application, and have no clue how to use it, so I was wondering if there's a way to do it without redux.
You don't need Redux. You just have to store the JWT in localStorage. To do that, just use localStorage.setItem('token', data.token) when you receive the login success response from the API. It's that simple. You can read this article for more details. It's for React Redux applications but doesn't need Redux.
Then, assuming you are using react-router, you need two helper functions. The first will go on your protected routes and would look like this:
const requireAuth = (...) => {
if(!localStorage.getItem('token')) {
// go to login route
}
// stay on this route since the user is authenticated
}
The second goes on your unprotected routes and looks like this:
const verifyAuth = (...) => {
if(localStorage.getItem('token')) {
// go to your dashboard or home route
}
// stay on this route since the user is not authenticated
}
Keep in mind that you have to refresh the token according to the server expiration time. A good approach would be to create a call to the server asking if the token is still valid.