(jwt refresh token) Persisting a globel state in reactjs - reactjs

I am currently using the django backend with jwt refresh token to persist a user login on my webpage. I have defined a refresh token hook here to get refresh token when the access token is expired or page is refreshed.
import Axios from '../utils/Axios';
import useAuth from './useAuth';
const useRefreshToken = () => {
const { setAuth } = useAuth();
const refresh = async () => {
const response = await Axios.post('account/auth/refresh/', {
'refresh': localStorage.getItem('refresh_token'),
withCredentials: true
});
setAuth(prev => {
return { ...prev, accessToken: response.data.access }
});
return response.data.access;
}
return refresh;
}
export default useRefreshToken;
After the user refreshed the page, it will trigger the refresh function to obtain another access token by sending out a refresh token to the api endpoint, and using setAuth to assign the new accessToken. And I realized that after I refreshed the page, the auth state will be emptied, making the spread operator of ...prev meaningless. Is there are any ways to presistent the current auth state after refreshing?
I don't really want to use localstore to do that, because my protected route condition depends on rather a user exist, so if I can just use localstore in here, I can just assign a user: 'whatever I type', it will still pass the auth?.user checking.

Related

How to logout user in React using context API

I am working with React hooks and created login logout functionality, but now I want user to logout when token is expired.
I am on a welcome page and using it, after two minutes if I do something else and the token is expired, I want to logout the user
I have created a context, Auth context where I have login logout context
I just want to call the logout function whenever the token expires
My authContext
const initialstate = {
user: null,
};
if (localStorage.getItem("JWT_Token")) {
const jwt_Token_decoded = Jwt_Decode(localStorage.getItem("JWT_Token"));
console.log(jwt_Token_decoded.exp * 1000);
console.log(Date.now());
if (jwt_Token_decoded.exp * 1000 < Date.now()) {
localStorage.clear(); // this runs only when I refresh the page or reload on route change it dosent work
} else {
initialstate.user = jwt_Token_decoded;
}
}
const AuthContext = createContext({
user: null,
login: (userData) => {},
logout: () => {},
});
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN":
return {
...state,
user: action.payload,
};
case "LOGOUT":
return {
...state,
user: null,
};
default:
return state;
}
};
const AuthProvider = (props) => {
const [state, dispatch] = useReducer(AuthReducer, initialstate);
const login = (userData) => {
localStorage.setItem("JWT_Token", userData.token);
dispatch({
type: "LOGIN",
payload: userData,
});
};
const logout = () => {
localStorage.clear();
dispatch({ action: "LOGOUT" });
};
return (
<AuthContext.Provider
value={{ user: state.user, login, logout }}
{...props}
/>
);
};
export { AuthContext, AuthProvider };
In the above code I am checking for expiry of token, but that only runs when page reloads, here I want to run it in every route change so that I can check for the token expiry.
I don't know how to do that and where to do that.
To logout I just need to call my logout context function, but I don't understand how to make the call.
I don't know If I have to do something in my Axios instance which is a separate file like below. Here I am creating one instance so that I can define my headers and other stuff at one place.
//global axios instance
import axios, { AxiosHeaders } from "axios"; // import axios from axios
const BASE_URL = "https://jsonplaceholder.typicode.com"; // server api
export default axios.create({
baseURL: BASE_URL,
headers: {
"Content-Type": "Application/json",
Access-token: "token here",
},
});
How can I approach this problem? I checked this question but in this example GraphQL has been used, so there is a function to set context where I can pass and use the store to dispatch my logout.
I have shared my axiosInstance code I think something needs to be added there. I am ready to use any approach that will generate some middleware, so that I can check for token in one place.
There are some points to consider in your approach
You can update the log out function to send the user to the login page, this way you guarantee this behavior whenever this user has been logged out;
Use the API response to validate when your token has expired, this way you don't need to keep watching the time of the token expiration and logout the user after any unauthorized API call;
Pay attention to this localStorage.clear() call, sometimes it can clear more than you want, and it is always a good idea to declare what you really want to clear. Eg.: sometimes you want to keep your user language, theme, or some UI configs that shouldn't be cleared after a logout;
Axios has an API to intercept your API call, this is a good way to do something before/after an API call (use it to logout after any unauthorized response)
If you really need to take care of this logout by the client and don't need to await the API response, you can try the setTimeout method (not recommended)

how to execute a component before another one in next.js?

I've been struggling with this problem for a while. I have an Auth component inside which I try to access to local storage to see if there is a token in there and send it to server to validate that token.
if token is valid the user gets logged-in automatically.
./components/Auth.tsx
const Auth: React.FC<Props> = ({ children }) => {
const dispatch = useDispatch(); // I'm using redux-toolkit to mange the app-wide state
useEffect(() => {
if (typeof window !== "undefined") {
const token = localStorage.getItem("token");
const userId = localStorage.getItem("userId");
if (userId) {
axios
.post("/api/get-user-data", { userId, token })
.then((res) => {
dispatch(userActions.login(res.data.user)); // the user gets logged-in
})
.catch((error) => {
localStorage.clear();
console.log(error);
});
}
}
}, [dispatch]);
return <Fragment>{children}</Fragment>;
};
export default Auth;
then I wrap every page components with Auth.tsx in _app.tsx file in order to manage the authentication state globally.
./pages/_app.tsx
<Provider store={store}>
<Auth>
<Component {...pageProps} />
</Auth>
</Provider>
I have a user-profile page in which user can see all his/her information.
in this page first of all I check if the user is authenticated to access this page or not.
if not I redirect him to login page
./pages/user-profile.tsx
useEffect(() => {
if (isAuthenticated) {
// some code
} else {
router.push("/sign-in");
}
}, [isAuthenticated]);
The problem is when the user is in user-profile page and reloads . then the user always gets redirected to login-page even if the user is authenticated.
It's because the code in user-profile useEffect gets executed before the code in Auth component.
(user-profile page is a child to Auth component)
How should i run the code in Auth component before the code in user-profile page ?
I wanna get the user redirected only when he's not authenticated and run all the authentication-related codes before any other code.
Are you sure that the problem is that user-profile's useEffect is executed before Auth's useEffect? I would assume that the outermost useEffect is fired first.
What most probably happens in your case is that the code that you run in the Auth useEffect is asynchronous. You send a request to your API with Axios, then the useEffect method continues to run without waiting for the result. Normally, this is a good situation, but in your profile, you assume that you already have the result of this call.
You would probably have to implement an async function and await the result of both the axios.post method and dispatch method. You would need something like this:
useEffect(() => {
async () => {
if (typeof window !== 'undefined') {
const token = localStorage.getItem("token")
const userId = localStorage.getItem("userId")
if (userId) {
try {
const resp = await axios.post("/api/get-user-data", {userId, token})
await dispatch(userActions.login(res.data.user)) // the user gets logged-in
} catch(error) {
localStorage.clear()
console.log(error)
}
}
}
}()
}, [dispatch])
I think this should work, but it would cause your components to wait for the response before anything is rendered.

Is it safe to temporarily store JWT in react native variable?

I'm currently using react-native-secure-storage to store JWTs for my user. The app retrieves data from an Express API so when making an HTTP request I retrieve the token from secure storage and when the async function completes the token is sent it to the API. I've run into a problem now which is that I want to make a component with an image in it served from the API but I can't get the JWT in an async function since the component needs to return immediately. My question is can I just temporarily store the JWT in a variable or in component state?
Yes, you can store it in a variable, in a context, or in a state. I would recommend using React.context() to propapate your JWT token.
Example:
const JwtStuffContext = React.createContext({
jwt: null,
});
const YourApp = () => {
const theToken = 'abc';
return <JwtStuffContext.Provider value={{ jwt: theToken }}>
{/** The rest of your app, or {children} */}
</JwtStuffContext.Provider>
}
const SomewhereDownTheReactTree = () => {
const { jwt } = useContext(JwtStuffContext);
}
Note that if you wanted to avoid sharing the JWT directly with your entire application, you could a function in your state that would proxy your API requests.
However, as you mentioned displaying an image, you could create a special Picture component that fetches the token from your store, and then requests the image from the API. Like so:
const Picture = ({ placeholderSrc, getSrcWithToken }) => {
const [token, setToken] = useState(null);
useEffect(() => {
const token = await yourFunctionToFetchTokenFromstore();
setToken(token);
}, [finalSrc]);
return <img src={token !== null ? getSrcWithToken(token) : placeholderSrc } />;
}
The above would display the placeholderSrc for as long as the token is unknown, when the token is known, it would call the prop function getSrcWithToken passing the token, allowing you to construct the URL if you need to.

How to send firebase refreshToken ? React

I am using firebase for the first time. (React app) I have 3 social Auth Provider for sign-in. Google, Facebook and Apple. Everything works great until here. But after 1 hour my token expires and I have to sign-out and sign-in again for refreshing my token. I saved the expiration time to my localStorage to check if token expires or not, if yes I invoke the signOut() function manually. But it doesn't solve the problem and not a good approach. I can't find how to refreshToken in firebase. And also, am I need to check expires again and send refreshToken or I have to refresh token on every time page refresh ?
import React from 'react'
import { useHistory } from "react-router";
import auth from "../utils/Auth";
export const useSocialAuth = () => {
const history = useHistory();
const providerFunc = (socialProvider:any) => {
const provider = socialProvider();
provider
.then((result: any) => {
console.log(result)
auth.login(() => {
localStorage.setItem('exp', result.user._delegate.stsTokenManager.expirationTime)
localStorage.setItem("userID", result.user.uid);
localStorage.setItem("tocaToken", result.user.multiFactor.user.accessToken);
history.push("/");
});
})
.catch((err:any) => console.log(err));
}
return providerFunc;
};
I solved the refresh token problem. All you guys need to add:
firebase.auth().currentUser.getIdToken(true)
When you make call from a browser .getIdToken(true) will automatically refresh your token. Make call like this:
firebase.auth().currentUser.getIdToken(/ forceRefresh / true)
.then(function(idToken) {
}).catch(function(error) {
});
More info: https://firebase.google.com/docs/reference/js/v8/firebase.User#getidtoken

Why is auth token not set in mobx store after login, and reroute to next component

I am using CoreUI with React Router DOM, React Router DOM config, Mobx and Mobx react.
I have written a login function in my store which stores the auth token from the server in session storage (using sessionStorage.setItem('token',token)) after successful login.
The problem, is that the next component I route to after login does a javascript fetch on componentDidMount, and sends the bearer auth token in the headers.
However, the issue that I am running into is that the auth token is undefined when the fetch occurs (sessionStorage.getItem('token')). It only shows up after I refresh the page. It is like the component loads and is rendered before the auth token has been saved.
I wonder if this is due to asynchronous calls.
In my login component I have this function:
logIn = async () => {
const { sessionStore: ss } = this.props.RootStore;
let login = await this.ss.login();
if(login) {
this.props.history.push('/dashboard');
}
}
Then, in my next component (Dashboard), I have:
componentDidMount() {
const { companyStore : company, sessionStore : ss } = this.props.RootStore;
if(ss.companySetup === false) {
company.getDefaultCompany('set');
}
}
In the function getDefaultCompany, I check if sessionStorage.getItem('token') is set, and if so, add it to the header. But it comes back undefined. If I refresh the page, the token is set and goes with the fetch call.
I am stumped.

Resources