How can I simulate that the auth token in local storage is expired?
This is the jwt:
export function parseJwt(token) {
const base64Url = token.split('.')[0];
return JSON.parse(window.atob(base64Url));
}
export function isTokenExpired(token) {
const t = parseJwt(token);
if (t.exp && Date.now() < t.exp * 1000) {
return false;
}
return true;
}
And this is localstorage:
export const isItemExpired = value => {
if (value.expiration && Date.now() < value.expiration) {
return false;
}
return true;
};
export const getItemExpiration = () => {
const d = new Date();
d.setDate(d.getDate());
return d.getTime();
};
export const setItem = (key, data, expiration) => {
const localStorageState = data;
if (localStorageState) {
localStorageState.expiration = expiration || getItemExpiration();
}
window.localStorage.setItem(key, JSON.stringify(localStorageState));
};
export const removeItem = key => {
window.localStorage.removeItem(key);
};
export const getItem = key => {
let value = null;
try {
value = JSON.parse(window.localStorage.getItem(key));
} catch (err) {
return null;
}
return value;
};
My app throws an error when the session refresh token is expired. I am new to tokens and auth in react. How can I set the expiration to be in 5 seconds after being created so that I can find out what the issue is?
Related
here i have pasted my whole code , here i have api which has 400 + responses and it need to store in asyncstorage like it need to store in local database this api is limited,and people also suggesting me to write this in async await but i am not aware of async await could u pls edit the code and help me out and the important thing is I am using mobx state tree to access this code in another file. thanks in advance.
import {makeAutoObservable, action} from 'mobx';
import {Dimensions, Platform} from 'react-native';
import AsyncStorage from '#react-native-async-storage/async-storage';
const {width, height} = Dimensions.get('window');
import {encode} from 'base-64';
class ProductStore {
constructor() {
makeAutoObservable(this);
}
screenWidth = width;
screenHeight = height;
headerHeight = 0;
isiOS = Platform.OS === 'ios';
isAndroid = Platform.OS === 'android';
isProductLoading = 'pending';
productData = [];
filterdData = [];
search = '';
isFlatlistRender = false;
setFields(eName, data) {
this[eName] = data;
console.log(eName, data);
}
// searchFilter = text => {
// if (text) {
// // setIsfilterdData(ProductStore.productData);
// const fData = this.productData;
// const newData = fData.filter(item => {
// const itemData =
// item.product || item.id
// ? item.product.toUpperCase() || item.id
// : ''.toUpperCase();
// const textData = text.toUpperCase();
// return itemData.indexOf(textData) > -1;
// });
// this.filterdData = newData;
// this.search = text;
// } else {
// this.isFlatlistRender = true;
// this.search = text;
// }
// };
getproductData = async () => {
if (this.isProductLoading == 'loading') {
return true;
}
this.isProductLoading = 'loading';
this.productData = [];
let headers = new Headers();
headers.set(
'Authorization',
'Basic ' + encode('username:password'),
);
fetch('some_url', {
method: 'GET',
headers: headers,
})
.then(response => response.json())
.then(responseJson => {
console.log('.....', responseJson);
AsyncStorage.setItem('dataKey', JSON.stringify(responseJson));
// retrieving data whenever you need from local storage
AsyncStorage.getItem('dataKey')
.then(responseJson => {
if (responseJson) {
let ourData = JSON.parse(responseJson);
console.log('ourData >>>>> ', ourData);
this.productData = ourData;
this.isProductLoading = 'done';
}
})
.catch(err => console.log('error >>>>> ', err));
// this.productData = ourData;
// this.isProductLoading = 'done';
})
.catch(error => {
console.error(error);
this.isProductLoading = 'error';
});
};
}
export default new ProductStore();
if you want to use mobx i guess there is middleware to save the state in localStorage like Redux-Persist on redux
but if you want to use AsyncStorage only then u can do this.
the save one its already correct.
this one is get
//save this code on helper or something
export const getItem = async ()=>{
try{
const data = await AsyncStorage.getItem('dataKey');
if(!data) throw new Error("Data doesn't exist yet")
return [true,JSON.parse(data)];
//first array is boolean to check if it success or not
//2nd array is data or error message (if false)
} catch (e){
return [false,e.message];
}
}
and then you call it inside a didmount or useEffect and save it to state
import {getItem} from './FileLocation';
//for class component
async ComponentDidMount(){
const [success,data] = await getItem();
if(success){
this.setState({data})
} else{
Alert.alert('Error',data)
}
}
//functional
const [data,setData] = React.useState([]);
const FetchItem = async ()=>{
const [success,data] = await getItem();
if(success){
setData(data)
} else{
Alert.alert('Error',data)
}
}
React.useEffect(()=>{
FetchItem();
},[])
I have this function get the token from api and check if I have admin permission. The problem is my export its firing before of the checking of the function.
const async = [
{ path: '*', redirect: '/404', hidden: true }
]
var hasAdmin = getAdmin()
if (hasAdmin === undefined || hasAdmin === null) {
var token = null
var url = null
var uri = window.location.href.split('?')
if (uri.length === 2) {
var vars = uri[1].split('&')
var getVars = {}
var tmp = ''
vars.forEach(function(v) {
tmp = v.split('=')
if (tmp.length === 2) {
getVars[tmp[0]] = tmp[1]
}
token = getVars.AUTH_ID
url = getVars.DOMAIN
})
getUserAdmin(url, token)
.then(response => {
var hasAdmin = response.result
if (hasAdmin === true) {
console.log('hasAdmin: ' + hasAdmin)
async.push(adminRouter)
}
})
.catch(error => {
console.log(error)
})
}
} else if (hasAdmin === true) {
async.push(adminRouter)
}
export const asyncRoutes = async
console.log('hasAdmin2: ' + hasAdmin)
Here is how Im using the const asyncRoutes:
import { asyncRoutes, constantRoutes } from '#/router'
/**
* Use meta.role to determine if the current user has permission
* #param roles
* #param route
*/
function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
return roles.some(role => route.meta.roles.includes(role))
} else {
return true
}
}
/**
* Filter asynchronous routing tables by recursion
* #param routes asyncRoutes
* #param roles
*/
export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
})
return res
}
const state = {
routes: [],
addRoutes: []
}
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
}
const actions = {
generateRoutes({ commit }, roles) {
return new Promise(resolve => {
let accessedRoutes
if (roles.includes('admin')) {
accessedRoutes = asyncRoutes || []
} else {
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
I always need reload(f5) the page in first access to gets the menu admin.
Await from promise dont works because de export should be always in the top. Some idea how can I fix this?
obs: attention in variable hasAdmin
I am trying to create a class that will fetch / cache users from my Firestore database. For some reason, I can't seem to save or expose the previous promise that was created. Here is my class:
export class UserCache {
private cacheTimeMilliseconds: number = 600000;
private userCache: any = {};
public getCacheUser(userid: string): Promise<User> {
return new Promise((resolve, reject) => {
let d = new Date();
d.setTime(d.getTime() - this.cacheTimeMilliseconds);
if (this.userCache[userid] && this.userCache[userid].complete && this.userCache[userid].lastAccess > d.getTime()) {
console.log("User cached");
resolve(this.userCache[userid].user);
}
console.log("Need to cache user");
this.userCache[userid] = {
complete: false
};
this.getSetUserFetchPromise(userid).then((data) => {
let user: User = <User>{ id: data.id, ...data.data() };
this.userCache[userid].user = user;
this.userCache[userid].complete = true;
this.userCache[userid].lastAccess = Date.now();
resolve(user);
});
});
}
private getSetUserFetchPromise(userid: string): Promise<any> {
console.log(this.userCache[userid]);
if (this.userCache[userid] && this.userCache[userid].promise) {
return this.userCache[userid].promise;
} else {
console.log("Creating new user fetch request.");
this.userCache[userid].promise = firestore().collection('users').doc(userid).get();
console.log(this.userCache[userid]);
return this.userCache[userid].promise;
}
}
}
Logs: (there are only 2 unique users, so should only be creating 2 new requests)
In the logs I can see that the promise is getting set in getSetUserFetchPromise, but the next time the function is called, the property is no longer set. I suspect it is either a scope or concurrency issue, but I can't seem to get around it.
I am calling getCacheUser in a consuming class with let oCache = new UserCache() and oCache.getCacheUser('USERID')
Edit following Tuan's answer below
UserCacheProvider.ts
import firestore from '#react-native-firebase/firestore';
import { User } from '../static/models';
class UserCache {
private cacheTimeMilliseconds: number = 600000;
private userCache: any = {};
public getCacheUser(userid: string): Promise<User> {
return new Promise((resolve, reject) => {
let d = new Date();
d.setTime(d.getTime() - this.cacheTimeMilliseconds);
if (this.userCache[userid] && this.userCache[userid].complete && this.userCache[userid].lastAccess > d.getTime()) {
console.log("User cached");
resolve(this.userCache[userid].user);
}
console.log("Need to cache user");
this.userCache[userid] = {
complete: false
};
this.getSetUserFetchPromise(userid).then((data) => {
let user: User = <User>{ id: data.id, ...data.data() };
this.userCache[userid].user = user;
this.userCache[userid].complete = true;
this.userCache[userid].lastAccess = Date.now();
resolve(user);
});
});
}
private getSetUserFetchPromise(userid: string): Promise<any> {
console.log(this.userCache[userid]);
if (this.userCache[userid] && this.userCache[userid].promise) {
return this.userCache[userid].promise;
} else {
console.log("Creating new user fetch request.");
this.userCache[userid].promise = firestore().collection('users').doc(userid).get();
console.log(this.userCache[userid]);
return this.userCache[userid].promise;
}
}
}
const userCache = new UserCache();
export default userCache;
ChatProvider.ts (usage)
let promises = [];
docs.forEach(doc => {
let message: Message = <Message>{ id: doc.id, ...doc.data() };
promises.push(UserCacheProvider.getCacheUser(message.senderid).then((oUser) => {
let conv: GCMessage = {
_id: message.id,
text: message.messagecontent,
createdAt: new Date(message.messagedate),
user: <GCUser>{ _id: oUser.id, avatar: oUser.thumbnail, name: oUser.displayname }
}
if (message.type && message.type == 'info') {
conv.system = true;
}
if (message.messageattachment && message.messageattachment != '') {
conv.image = message.messageattachment;
}
return conv;
}));
});
Promise.all(promises).then((values) => {
resolve(values);
});
Without seeing the calling code, it could be that getCacheUser is called twice before firestore resolves.
As an aside, I think refactoring the class may make debugging easier. I wonder why it caches the user, promise completion status, and the promise itself. Why not just cache the promise, something like:
interface UserCacheRecord {
promise: Promise<User>
lastAccess: number
}
export class UserCache {
private cacheTimeMilliseconds: number = 600000;
private userCache: { [userid: string]: UserCacheRecord } = {};
public async getCacheUser(userid: string): Promise<User> {
let d = new Date();
const cacheExpireTime = d.getTime() - this.cacheTimeMilliseconds
if (this.userCache[userid] && this.userCache[userid].lastAccess > cacheExpireTime) {
console.log("User cached");
return this.userCache[userid].promise
}
console.log("Need to cache user");
this.userCache[userid] = {
promise: this.getUser(userid),
lastAccess: Date.now()
}
return this.userCache[userid].promise
}
private async getUser(userid: string): Promise<User> {
const data = firestore().collection('users').doc(userid).get();
return <User>{ id: data.id, ...data.data() };
}
}
Currently, you create new UserCache everytime you access cache users. You have to export the instance of UserCache class, so just single instance is used for your app.
UserCache.ts
class UserCache {
}
const userCache = new UserCache();
export default userCache;
SomeFile.ts
import UserCache from './UserCache';
UserCache.getCacheUser('USERID')
Update
Added some tests
class UserCache {
userCache = {};
getUser(id) {
return new Promise((resolve, reject) => {
if (this.userCache[id]) {
resolve({
...this.userCache[id],
isCache: true,
});
}
this.requestUser(id).then(data => {
resolve(data);
this.userCache[id] = data;
});
});
}
requestUser(id) {
return Promise.resolve({
id,
});
}
}
const userCache = new UserCache();
export default userCache;
userCache.test.ts
import UserCache from '../test';
describe('Test user cache', () => {
test('User cached successfully', async () => {
const user1: any = await UserCache.getUser('test1');
expect(user1.isCache).toBeUndefined();
const user2: any = await UserCache.getUser('test1');
expect(user2.isCache).toBe(true);
});
});
I want to logout the session of the current user after one hour of his login as well as on click of a button. I tried storing the current time stamp of the user as soon as he login using his auth-token. But got confused in achieving the proper result.
Here is the code :
Get from LocalStorage:
export default function setLocalData(key, value)
{
var responseStatus = false;
switch (key)
{
case 'currentUser':
const currentDateTime = new Date();
const updateDateTime = new Date();
const expireDateTime = new Date(updateDateTime.setHours(updateDateTime.getHours() + 2));
const currentTimestamp = Math.floor(currentDateTime.getTime() / 1000);
const expireTimeStamp = Math.floor(expireDateTime.getTime() / 1000);
const initialState = {
isLogin: true,
loginTime: currentTimestamp,
expirationTime: expireTimeStamp,
userInfo: value
};
localStorage.setItem(key, btoa(JSON.stringify(initialState)));
responseStatus = true;
break;
default:
responseStatus = false;
break;
}
return responseStatus;
}
set to LocalStorage:
export default function getLocalData(key, type='all')
{
var responseObject = null;
try
{
if(localStorage.getItem(key))
{
var response;
response = JSON.parse(atob(localStorage.getItem(key)));
switch (type)
{
case 'all':
responseObject = (response) ? response : null;
break;
default:
responseObject = null;
break;
}
}
}
catch (e)
{
responseObject = null;
}
return responseObject;
}
This is my component file where the automatic logout function needs to trigger:
class DefaultLayout extends Component {
componentDidMount(){
let token = LocalData.getLocalData('currentUser');
console.log(token);
setTimeout(()=> {
this.signOut();
}, token.expirationTime);
}
//this is triggered on clicking on logout() button
signOut(e) {
e.preventDefault();
localStorage.clear();
this.props.history.push('/login')
}
render() {
//console.log(this.props)
return ( ......
......
}
}
On console.log(token), the result achieved is:
{
expirationTime: 1575286437
isLogin: true
loginTime: 1575279237
userInfo: "eyJhbGciOiJIUz11NiIsInR5cCI6IkpXVCJ9.....
}
I am not sure if am implementing this correctly. Kindly help to figure this out.
I think the problem is here
setTimeout(()=> {
this.signOut();
}, token.expirationTime);
You are setting the timeout value to the expiration time. It should be an interval in milliseconds. So if you want the function to be triggered after 1 hr then the value should be 60 * 60 * 1000 or some calculation based on the expiration time stamp.
Look at this
class DefaultLayout extends Component {
componentDidMount(){
let token = LocalData.getLocalData('currentUser');
console.log(token);
if(token.expirationTime>==token.loginTime){
this.signOut();
}
}
//this is triggered on clicking on logout() button
signOut() {
localStorage.clear();
this.props.history.push('/login')
}
render() {
//console.log(this.props)
return ( ......
......
}
}
After logging in the scheduleRefresh() function is called and it will continuously refresh the token. The problem occurs when I refresh the page, or when an authenticated user tries accessing the web page again. startupTokenRefresh is called on startup, the token is refreshed, but the token is never scheduled to be refreshed again like its suppose to. If I refresh the page after I know the token has expired I get a token_not_provided error but I can see in the network console the token is being refreshed a couple of seconds after this error or the page has loaded. If I refresh again the request to my api is made fine but again the token does not refresh after its expired.
What am I doing wrong?
app.component.ts (on app start up)
platform.ready().then(() => {
storage.ready().then(() => storage.get('token'))
.then(token => {
storage.set('token', token);
authService.token = token;
authService.authNotifier.next(true);
authService.checkToken();
authService.startupTokenRefresh();
});
authService.authenticationNotifier().subscribe((authed) => {
if (authed) {
this.rootPage = TabsPage;
} else {
authService.logout();
this.rootPage = LoginPage;
}
});
}
auth.service.ts
jwtHelper: JwtHelper = new JwtHelper();
token;
refreshSubscription: any;
authNotifier: ReplaySubject<boolean> = new ReplaySubject<boolean>(1);
authenticationNotifier(): Observable<boolean> {
return this.authNotifier;
}
refresh(): Observable<any> {
console.log("in refresh()");
let URL = `${myApi}/refresh?token=${this.token}`;
return this.authHttp.get(URL)
.map((rsp) => {
this.token = rsp.json().token;
this.storage.ready().then(() => this.storage.set('token', this.token));
this.authNotifier.next(true);
return rsp.json().token;
},
err => {
this.authNotifier.next(false);
this.logout();
console.log(err);
})
.share();
}
checkToken() {
if (this.token === '' || this.token === null || this.token === undefined) {
this.authNotifier.next(false);
this.logout();
}
}
public scheduleRefresh() {
if (this.token === '' || this.token === null || this.token === undefined) {
this.authNotifier.next(false);
this.logout();
}
else {
// If the user is authenticated, use the token stream provided by angular2-jwt and flatMap the token
let source = this.authHttp.tokenStream.flatMap(
token => {
let jwtIat = this.jwtHelper.decodeToken(this.token).iat;
let jwtExp = this.jwtHelper.decodeToken(this.token).exp;
let iat = new Date(0);
let exp = new Date(0);
let delay = (exp.setUTCSeconds(jwtExp) - iat.setUTCSeconds(jwtIat));
return Observable.interval(delay);
});
this.refreshSubscription = source.subscribe(() => {
this.refresh().subscribe((res) => console.log('-> Refreshed...'),
(error) => console.log('Refresh error: ' + JSON.stringify(error)))
});
}
}
public startupTokenRefresh() {
if (this.token === '' || this.token === null || this.token === undefined) {
this.authNotifier.next(false);
this.logout();
}
else {
// Get the expiry time to generate a delay in milliseconds
let now: number = new Date().valueOf() / 1000;
let jwtExp: number = this.jwtHelper.decodeToken(this.token).exp;
let iat: number = this.jwtHelper.decodeToken(this.token).iat;
let refreshTokenThreshold = 10; //seconds
let delay: number = jwtExp - now;
let totalLife: number = (jwtExp - iat);
(delay < refreshTokenThreshold ) ? delay = 1 : delay = delay - refreshTokenThreshold;
// Use the delay in a timer to // run the refresh at the proper time
return Observable.timer(delay * 1000);
});
// Once the delay time from above is reached, get a new JWT and schedule additional refreshes
source.subscribe(() => {
this.refresh().subscribe(
(res) => {
console.log('-> Refreshed on startup');
this.scheduleRefresh();
},
(error) => console.log('-> Refresh error:' + JSON.stringify(error)))
});
}
}
public unscheduleRefresh() {
console.log("unsched");
if (this.refreshSubscription) {
this.refreshSubscription.unsubscribe();
}
}
login.ts
onLogin() {
this.authService.login(this.loginForm.value.username, this.loginForm.value.password)
.subscribe(
(response) => {
this.storage.ready().then(() => this.storage.set('token', response.token));
this.authService.token = response.token;
this.authService.authNotifier.next(true);
},
error => {
console.log(error);
this.loginError = true;
this.authService.authNotifier.next(false);
},
() => {
console.log("login success");
this.authService.scheduleRefresh();
this.navCtrl.push(TabsPage);
},
);
}
}