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 ( ......
......
}
}
Related
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?
I am working on project where I am invalidating browser cache but when I call this function window.location.reload() it refresh page again and again . I want to reload page at once . Could someone please help me how to stop page from refreshing again and again . Thanks
Note: I am using React.JS
Code
import React from "react";
import packageJson from "../package.json";
global.appVersion = packageJson.version;
// version from response - first param, local version second param
const semverGreaterThan = (versionA, versionB) => {
const versionsA = versionA.split(/\./g);
const versionsB = versionB.split(/\./g);
while (versionsA.length || versionsB.length) {
const a = Number(versionsA.shift());
const b = Number(versionsB.shift());
// eslint-disable-next-line no-continue
if (a === b) continue;
// eslint-disable-next-line no-restricted-globals
return a > b || isNaN(b);
}
return false;
};
class CacheBuster extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
isLatestVersion: false,
refreshCacheAndReload: (caches) => {
if (caches) {
caches.keys().then(async function (names) {
await Promise.all(names.map((name) => caches.delete(name)));
});
}
window.location.reload();
},
};
}
componentDidMount() {
fetch(`/meta.json?${new Date().getTime()}`, { cache: "no-cache" })
.then((response) => response.json())
.then((meta) => {
const latestVersion = meta.version;
const currentVersion = global.appVersion;
const shouldForceRefresh = semverGreaterThan(
latestVersion,
currentVersion
);
if (shouldForceRefresh) {
console.log(
`We have a new version - ${latestVersion}. Should force refresh`
);
this.setState({ loading: false, isLatestVersion: false });
} else {
console.log(
`You already have the latest version - ${latestVersion}. No cache refresh needed.`
);
this.setState({ loading: false, isLatestVersion: true });
}
});
}
render() {
const { loading, isLatestVersion, refreshCacheAndReload } = this.state;
return this.props.children({
loading,
isLatestVersion,
refreshCacheAndReload,
});
}
}
export default CacheBuster;
if (caches) {...} else (window.location.reload(true));
This code is struck between constructor and the render.
this.state.refreshCacheAndReload should be set to null in constructor, where in you choose to reload it in componentdidmount whenever the version mismatches.
My SPA has this working component that fetches an access token that will be encrypted and passed to other components via props. This is it:
import React, { Component } from 'react';
//import { Redirect } from 'react-router-dom';
import axios from 'axios';
import Credentials from './spotify-auth.js'
import './Spotify.css'
class SpotifyAuth extends Component {
constructor (props) {
super(props);
this.state = {
isAuthenticatedWithSpotify: false,
};
this.state.handleRedirect = this.handleRedirect.bind(this);
};
generateRandomString(length) {
let text = '';
const possible =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
getHashParams() {
const hashParams = {};
const r = /([^&;=]+)=?([^&;]*)/g;
const q = window.location.hash.substring(1);
let e = r.exec(q);
while (e) {
hashParams[e[1]] = decodeURIComponent(e[2]);
e = r.exec(q);
}
return hashParams;
}
componentDidMount() {
//if (this.props.isAuthenticated) {
const params = this.getHashParams();
const access_token = params.access_token;
const state = params.state;
const storedState = localStorage.getItem(Credentials.stateKey);
localStorage.setItem('spotifyAuthToken', access_token);
localStorage.getItem('spotifyAuthToken');
if (window.localStorage.getItem('authToken')) {
this.setState({ isAuthenticatedWithSpotify: true });
};
if (access_token && (state == null || state !== storedState)) {
alert('Click "ok" to finish authentication with Spotify');
} else {
localStorage.removeItem(Credentials.stateKey);
}
// DO STUFF WITH ACCEES TOKEN HERE
this.props.onConnectWithSpotify(access_token);
};
handleRedirect(event) {
event.preventDefault()
this.props.createMessage('You linked your Spotify account!', 'success');
// get client features at authenticating
const params = this.getHashParams();
const access_token = params.access_token;
console.log(access_token);
const state = this.generateRandomString(16);
localStorage.setItem(Credentials.stateKey, state);
let url = 'https://accounts.spotify.com/authorize';
url += '?response_type=token';
url += '&client_id=' + encodeURIComponent(Credentials.client_id);
url += '&scope=' + encodeURIComponent(Credentials.scope);
url += '&redirect_uri=' + encodeURIComponent(Credentials.redirect_uri);
url += '&state=' + encodeURIComponent(state);
window.location = url;
};
render() {
return (
<div className="button_container">
<h1 className="title is-3"><font color="#C86428">{"Welcome"}</font></h1>
<div className="Line" /><br/>
<button className="sp_button" onClick={(event) => this.handleRedirect(event)}>
<strong>LINK YOUR SPOTIFY ACCOUNT</strong>
</button>
</div>
)
}
}
export default SpotifyAuth;
This token credentials last for 60 minutes.
I've learned that the standard option for SPAs is to use iframes to silently renew tokens and not use refresh tokens at all.
How do one spin up an iframe and silently get a new access token every hour in a React component like the one above? I have looked everywhere for this and haven't found anything.
You can do the following:
Create some watcher function, which checks the expiration time of the access token. If the token is about to expire, it is time to renew it.
Render an iframe tag, the src should be the same URL which you are using for redirecting to the Auth server, with one difference: change the return URL to a static file, let's call it redirect.html. The server should know about the user calling this URL, from the stored cookie, so it should just simply redirect you to the redirect.html file, now with a fresh access token.
In this redirect.html write a short script, which takes out the token from the URL and override it with the one you already have in local storage.
Destroy the iframe.
This is about it, the token is renewd. Keep the watcher going and renew it everytime its about to expire (do it like 5 minutes before it expire).
An example implementation for the AccessToken component in React, most parts of the code will actually work, but you need to replace the constants with your stuff. Also, some functions like extractTokenFromUrl are missing, but that should be easy enough to make:
import React, { Component } from 'react'
export class SilentTokenRenew extends Component {
constructor(props) {
super(props)
this.state = { renewing: false }
this.currentAttempt = 0
this.maxNumberOfAttempts = 20
}
shouldComponentUpdate(nextProps, nextState) {
return this.state.renewing !== nextState.renewing
}
componentDidMount() {
this.timeInterval = setInterval(this.handleCheckToken, 20000)
}
componentWillUnmount() {
clearInterval(this.timeInterval)
}
willTokenExpire = () => {
const token = YOUR_ACCESS_TOKEN_OBJECT // { accessToken, expirationTime }
const threshold = 300 // 300s = 5 minute threshold for token expiration
const hasToken = token && token.accessToken
const now = (Date.now() / 1000) + threshold
return !hasToken || (now > token.accessToken.expirationTime)
}
handleCheckToken = () => {
if (this.willTokenExpire()) {
this.setState({ renewing: true })
clearInterval(this.timeInterval)
}
}
silentRenew = () => {
return new Promise((resolve, reject) => {
const checkRedirect = () => {
// This can be e
const redirectUrl = localStorage[YOUR_REDIRECT_URL_FROM_THE_REDIRECT_HTML_FILE] // /redirect.html#access_token=......
if (!redirectUrl) {
this.currentAttempt += 1
if (this.currentAttempt > this.maxNumberOfAttempts) {
reject({
message: 'Silent renew failed after maximum number of attempts.',
short: 'max_number_of_attempts_reached',
})
return
}
setTimeout(() => checkRedirect(), 500)
return
}
// Clean up your localStorage for the next silent renewal
localStorage.removeItem(YOUR_REDIRECT_URL_FROM_THE_REDIRECT_HTML_FILE)
// Put some more error handlers here
// Silent renew worked as expected, lets update the access token
const session = extractTokenFromUrl(redirectUrl) // write some function to get out the access token from the URL
// Following your code you provided, here is the time to set
// the extracted access token back to your localStorage under a key Credentials.stateKey
localStorage.setItem(Credentials.stateKey, JSON.stringify(session))
resolve(session)
}
checkRedirect()
})
}
handleOnLoad = () => {
this.silentRenew()
.then(() => {
this.setState({ renewing: false })
this.currentAttempt = 0
this.timeInterval = setInterval(this.handleCheckToken, 60000)
// Access token renewed silently.
})
.catch(error => {
this.setState({ renewing: false })
// handle the errors
})
}
renderIframe = () => {
const url = new URL(YOUR_AUTHORIZE_URL_TO_TH_AUTH_SERVER)
url.searchParams.set('redirect_uri', 'http://localhost:3000/redirect.html') // the redirect.html file location
url.searchParams.set('prompt', 'none')
return (
<iframe
style={{ width: 0, height: 0, position: 'absolute', left: 0, top: 0, display: 'none', visibility: 'hidden' }}
width={0}
height={0}
title="silent-token-renew"
src={url.href}
onLoad={this.handleOnLoad}
/>
)
}
render() {
const { renewing } = this.state
return renewing ? this.renderIframe() : null
}
}
Sample code fore the redirect.html file:
<!DOCTYPE html>
<html>
<head>
<title>OAuth - Redirect</title>
</head>
<body>
<p>Renewing...</p>
<script>
// Get name of window which was set by the parent to be the unique request key
// or if no parameter was specified, we have a silent renew from iframe
const requestKey = YOUR_REDIRECT_URL_FROM_THE_REDIRECT_HTML_FILE;
// Update corresponding entry with the redirected url which should contain either access token or failure reason in the query parameter / hash
window.localStorage.setItem(requestKey, window.location.href);
window.close();
</script>
</body>
</html>
I am trying to logout the user in my react app automatically. I am receiving token from backend API that expires after 1 hour. I am storing the received token in my local storage as soon as the user login. My protected route 'DefaultLayout.js' stores the logout function and is rendered after the user logs in successfully. Currently I am serving my app on localhost but I want to implement this functionality on production also. The problem I am facing:
Implementing automatic logout not working even after one hour of login session.
Here is my code snippets:
DefaultLAyout.js
class DefaultLayout extends Component {
loading = () => <div className="animated fadeIn pt-1 text-center">Loading...</div>
componentDidMount(){
let userInfo = LocalData.getLocalData('currentUser', 'all');
console.log(userInfo);
if (userInfo.success === false && userInfo.message === 'Failed to authenticate user' && userInfo.responseStatus === 403){
localStorage.clear();
this.props.history.push('/login')
}
else if(userInfo.userToken === null){
localStorage.clear();
this.props.history.push('/login')
}
}
signOut(e) {
//e.preventDefault();
localStorage.clear();
this.props.history.push('/login')
}
render() {
//console.log(this.props)
return (
<div className="app">
<AppHeader fixed>
<Suspense fallback={this.loading()}>
<DefaultHeader onLogout={e=>this.signOut(e)}/>
</Suspense>
</AppHeader>
<div className="app-body">
<AppSidebar fixed display="lg">
<AppSidebarHeader />
<AppSidebarForm />
<Suspense>
{LocalData.getLocalData("currentUser", 'isLogin') === true ? <AppSidebarNav navConfig={navigationProtected} {...this.props} router={router}/> : <AppSidebarNav navConfig={navigation} {...this.props} router={router}/>}
</Suspense>
<AppSidebarFooter />
<AppSidebarMinimizer />
</AppSidebar>
...... }
}
Login.js
class Login extends Component {
// _isMounted = false;
constructor(){
super();
this.state = {
isLogged: false,
email: '',
password: '',
isSuccess: true,
isLoggedInSuccess: false,
}
}
onChange = (e) => {
e.preventDefault();
this.setState({ [e.target.name]: e.target.value });
}
loginUser = async (e) => {
const { email, password} = this.state;
e.preventDefault();
await axios.post(GlobalVar.BASE_URL+'api/user/login', { email, password })
.then(res => {
const loginResponse = res;
console.log(loginResponse);
if(loginResponse.status === 200 && loginResponse.data.success === true){
LocalData.setLocalData("currentUser", loginResponse);
this.setState({
isLoggedInSuccess: true,
isSuccess: res.data.success
}, () => this.props.history.push('/'));
}
else{
const error = new Error('Invalid Login!');
this.setState({
isLoggedInSuccess: false
});
throw error;
}
})
.catch(err => {
console.log(err);
this.setState({isLoggedInSuccess: false, isSuccess: false});
});
}
...}
setLocalstorage.js
export default function setLocalData(key, value)
{
var responseStatus = false;
switch (key)
{
case 'currentUser':
const initialState = {
isLogin: true,
userToken: value.data.data,
responseStatus: value.status,
message: value.data.message,
success: value.data.success
};
localStorage.setItem(key, btoa(JSON.stringify(initialState)));
responseStatus = true;
break;
default:
responseStatus = false;
break;
}
return responseStatus;
}
getLocalStorage.js
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;
case 'isLogin':
responseObject = (response.isLogin);
break;
case 'successMsg':
responseObject = (response.message);
break;
case 'getToken':
responseObject = (response) ? response.userToken : null;
break;
case 'getResponseStatus':
responseObject = (response) ? response.responseStatus : null;
break;
case 'getSuccessStatus':
responseObject = (response) ? response.success : false;
break;
case 'getFormData':
responseObject = (response) ? response : null;
break;
default:
responseObject = null;
break;
}
}
}
catch (e)
{
responseObject = null;
}
return responseObject;
}
Data response from LocalStorage(consoled in DefaultLayout.js)
{
isLogin: true
message: "User successfully logged in"
responseStatus: 200
success: true
userToken: "eyJhbGciOiJ..."
__proto__: Object
}
I have tried lots of workaround but nothing seems to solve my problem. Kindly help out this soon.
//you can implement auto-logout by the following code
let hours = 1;
let saved = localStorage.getItem(....);
if (saved && (new Date().getTime() - saved > hours * 60 * 60 * 1000)) {
localStorage.clear()
}
// Increase expiration time after save
localStorage.setItem('saved', new Date().getTime())
// you can check logout status while making any call, if its more than an hour you can clear localstorage & redirect
I'm trying to make a forecast app with React and Flux. I fetch the data from Yahoo Weather API, and put the data to my store with a callback in jsonp request.Then in the View, I get the data (in componentDidMount())from store as a state and pass some properties of it to child components.
The data(this.state.store), which is a Object, has two properties, called condition and forecast.The problem is that if I want to pass the this.state.store.condition(or forecast) to the child, it says TypeError: Cannot read property 'condition' of undefined. But if I just try to access this.state.store(for example, console.log(this.state.store)), there is no error.
Also, if I try to access this.state.store.condition in a try-catch statement, and log the error when there is one, I do access the condition successfully with the console printed TypeError above mentioned.
Here is my codes:
store:
const CHANGE_EVENT = 'change';
let _app = {};
// create a city
function create(city, data) {
_app[city.toUpperCase()] = {
condition: data.condition,
forecast: data.forecast,
};
}
const AppStore = Object.assign({}, EventEmitter.prototype, {
getAll() {
return _app;
},
emitChange() {
this.emit(CHANGE_EVENT);
},
addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
});
// register callback
AppDispatcher.register((action) => {
switch (action.actionType) {
case AppConstants.CREATE_CITY: {
create(action.city, action.data);
AppStore.emitChange();
break;
}
// other cases
default:
// noop
}
});
actions:
function callback(city, data) {
console.log(data);
const action = {
actionType: AppConstants.CREATE_CITY,
city,
data,
};
AppDispatcher.dispatch(action);
}
const AppActions = {
create(city) {
getDataFromAPI(city, callback);
},
};
utils:
function getDataFromAPI(query, callback) {
let data;
const url = `https://query.yahooapis.com/v1/public/yql?q=select * from weather.forecast where u='c' AND woeid in (select woeid from geo.places(1) where text="${query}")&format=json`;
superagent
.get(url)
.use(jsonp)
.end((err, res) => {
console.log(res.body.query.results.channel.item);
data = res.body.query.results.channel.item;
callback(query, data);
});
}
views:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
store: Store.getAll(),
currentCity: 'BEIJING',
};
this.onChange = this.onChange.bind(this);
this.getCurrentCity = this.getCurrentCity.bind(this);
}
componentWillMount() {
AppActions.create('BEIJING');
}
componentDidMount() {
Store.addChangeListener(this.onChange);
}
onChange() {
this.setState({ store: Store.getAll() });
}
getCurrentCity(city) {
this.setState({ currentCity: city.toUpperCase() });
}
componentWillUnmout() {
Store.removeChangeListener(this.onChange);
}
render() {
// For now, I have to do all of these to pass the condition to the child component
let condition;
let forecast;
let text;
let temp;
let currentWeatherCode;
let forecastWeatherCode = [];
let currentWeatherClassName;
let forecastWeatherClassName = [];
let date;
let forecastDate = [];
console.log(this.state.store[this.state.currentCity]);<--NO ERROR
// console.log(this.state.store[this.state.currentCity])<--UNDEFINED
// console.log(this.state.store[this.state.currentCity].condition);<--CANNOT READ PROPERTY
^
|
ERROR ON THIS 2 STATEMENTS
try {
condition = this.state.store[this.state.currentCity].condition;
forecast = this.state.store[this.state.currentCity].forecast;
text = condition.text.toUpperCase();
temp = condition.temp;
currentWeatherCode = condition.code;
currentWeatherClassName = setWeatherIcon(currentWeatherCode);
date = condition.date;
for (let i = 0; i < 6; i++) {
forecastWeatherCode.push(forecast[i].code);
forecastWeatherClassName.push(setWeatherIcon(forecastWeatherCode[i]));
forecastDate.push(forecast[i].date);
}
} catch (err) {
console.log(err);<--STILL ERROR, BUT I DO ACCESS THE PROP CONDITION IN THIS WAY
}
return (
<div>
<Today
city={this.state.currentCity}
weatherStatus={text}
tempreture={temp}
currentWeatherClassName={currentWeatherClassName}
date={date}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.querySelector('#app'));
It seems to me that you are trying to access the this.state.store[this.state.currentCity] property before it is fetched from the remote API.
You could add some sort of indication that the data is still being fetched like this.
render() {
// For now, I have to do all of these to pass the condition to the child component
let condition;
let forecast;
let text;
let temp;
let currentWeatherCode;
let forecastWeatherCode = [];
let currentWeatherClassName;
let forecastWeatherClassName = [];
let date;
let forecastDate = [];
console.log(this.state.store[this.state.currentCity]);<--NO ERROR
if (!this.state.store.hasOwnProperty(this.state.currentCity)) {
return <div>Loading...</div>;
}
... the rest of your original code
}
When it is done loading the setState() method is invoked and render() is called again. The second time it will fall trough the if and run your code.