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
Related
I am trying to have login page with using react typescript which i am using state management with Mobx and also make refresh token method as follow, my problem is before token expires out, the refresh token cant be trigerred by token time ends up. my login method is in userstore.tsx like as:
login = async (creds: UserFormValues) => {
try {
const user = await agent.Account.login(creds);
store.commonStore.setToken(user.token);
this.startRefreshTokenTimer(user);
runInAction(() => this.user = user);
window.location.href = '/dashboard'
} catch (error) {
throw error;
}
}
login method works without any hesitation after that refresh token timer works and runs refreshtoken method as follow;
refreshToken = async () => {
this.stopRefreshTokenTimer();
try {
const user = await agent.Account.refreshToken();
runInAction(() => this.user = user);
store.commonStore.setToken(user.token);
this.startRefreshTokenTimer(user);
} catch (error) {
console.log(error);
}
}
private startRefreshTokenTimer(user: User) {
const jwtToken = JSON.parse(atob(user.token.split('.')[1]));
const expires = new Date(jwtToken.exp * 1000);
const timeout = expires.getTime() - Date.now();
alert(expires.getTime() - Date.now())
this.refreshTokenTimeout = setTimeout(this.refreshToken, timeout);
}
private stopRefreshTokenTimer() {
clearTimeout(this.refreshTokenTimeout);
}
after time is up, i was waiting, refreshtoken method was trigerred but it wouldnt be possible after i gave even 1 min short token expire time and waited 1 min for triggered itself. my root component App.tsx is also as follow;
import 'devextreme/dist/css/dx.softblue.css';
import { observer } from 'mobx-react-lite';
import { Suspense } from 'react'
import { Outlet } from 'react-router-dom'
import { I18nProvider } from '../_metronic/i18n/i18nProvider'
import { LayoutProvider, LayoutSplashScreen } from '../_metronic/layout/core'
import { MasterInit } from '../_metronic/layout/MasterInit'
const App = observer(() => {
return (
<Suspense fallback={<LayoutSplashScreen />}>
<I18nProvider>
<LayoutProvider>
<Outlet />
<MasterInit />
</LayoutProvider>
</I18nProvider>
</Suspense>
)
})
export { App }
I think i skipped some thing i have to do, if anybody helps and wants me to be clearer and extra details, i can give more information about the issue which i got stuck.
this problem is not related to mobx. i solve this problem in the first code above that i realize i used here window.location.href = '/dashboard' and when the component changes the javascript reloads. the prevent the issues is that it should use history.push('/dashboard'). i use history version 6 and this version is a bit different than the earlier version, to see this usage, click the link here.
i take note this due that this might be helpful context if anybody gets the kind of issue
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.
Using oidc in react:
import { useAuth } from "react-oidc-context";
//This is inside AuthProvider from react-oidc-context
const MyComponent: React.FC<MyComponentProps> = () => {
const auth = useAuth();
auth.events.addAccessTokenExpiring(() => {
auth.signinSilent().then(user => {
console.log('signinSilent finished', user);
//this is where I reset auth token for http headers because it is being stored.
}).catch(error => {
console.log('signinSilent failed', error);
});
});
}
The config being used for OIDC is pretty simple:
const oidcConfig = {
authority: authConfig.authority,
client_id: authConfig.client_id,
redirect_uri: authConfig.redirect_uri,
scope: "openid offline_access",
};
This all ends up working. The addAccessTokenExpiring fires when the token is about done and I the signinSilent gets me a new one and I can reset my headers and then 401s won't happen when someone sits idle on the page for an hour.
The problem is signinSilent causes a refresh of the page to happen. If someone is sitting for an hour idle on the page, a refresh would most likely go unnoticed... However, if a form was halfway complete and they stepped away or something, that would just be gone on the page refresh.
Is there anyway to prevent the signinSilent from refreshing the page and actually just silently renewing the token?
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.
I am doing the below in my App.JS but it is not refreshing the token in the other components. Can some one suggest what would be the best way to check if the token is valid or refresh it from all the components before the AXIOS call is made. If you could provide a link to any example it would be great. Thank you.
import { Auth } from "aws-amplify";
import setAuthorizationToken from './components/utility/SetAuthorizationToken';
async componentWillMount() {
try {
console.log("Im am in componentWillMount() in App.js");
await Auth.currentSession().then(data => {
this.setAuthStatus(true);
}).catch(err => {
this.setAuthStatus(false)
});
await Auth.currentAuthenticatedUser().then(user => {
this.setUser(user);
setAuthorizationToken(user.signInUserSession.idToken.jwtToken);
});
} catch(error) {
if (error !== 'No current user') {
console.log(error);
}
}
this.setState({ isAuthenticating: false });
}
SetAuthorizationToken.js
import axios from 'axios';
export default function SetAuthorizationToken(token) {
if(token){
axios.defaults.headers.common['Authorization'] = token;
}else{
delete axios.defaults.headers.common['Authorization'];
}
}
H, welcome to SO.
You do not have to track the JWT token or user or refresh it by yourself with cognito.
For the axios call just use await Auth.currentSession() before the axios call and inject the token directly from the callback into your axios call. currentSession will only return a valid token and will try to refresh it, if it is expeired.
That way, you can rely on AWS to always provide you with a valid token without tracking it yourself.
You also do not have to save the user since you can access him anywhere you need it with the currentAuthenticatedUser call.
Hope this helps. Happy coding.