Reputation: 6451
Problem:
Every time when I log in, the id token which is obtained by Auth.signIn will be store in localStorage.
After I login, UI make requests which require Authorization(use id token),
but it failed every time.
I tried to copy the id token in localStorage and tried the same API request in Postman,
below error message shown.
the incoming token has expired
But When I reload the page, the request is sent successfully and receive ok response.
I am not sure whether it's because the token refreshing logic is not correct in my code.
I just put the token refreshing logic in App.js componentDidMount().
The logic is based on below post.
how handle refresh token service in AWS amplify-js
Can someone let me know what's wrong of my code?
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
//aws
import Amplify from 'aws-amplify';
import config from './config.json'
const Index = () => {
Amplify.configure({
Auth: {
mandatorySignId: true,
region: config.cognito.REGION,
userPoolId: config.cognito.USER_POOL_ID,
userPoolWebClientId: config.cognito.APP_CLIENT_ID
}
});
return(
<React.StrictMode>
<App/>
</React.StrictMode>
)
}
ReactDOM.render(
<Index />,
document.getElementById('root')
);
serviceWorker.unregister();
App.js
import React, { Component } from 'react';
import { Switch, Route } from 'react-router-dom';
import { Redirect } from 'react-router';
import { withRouter } from 'react-router-dom';
import config from './config.json'
//Screen
import Login from './screen/auth/Login'
import Drawer from './components/Drawer'
import { Auth } from 'aws-amplify';
const AmazonCognitoIdentity = require('amazon-cognito-identity-js');
const CognitoUserPool = AmazonCognitoIdentity.CognitoUserPool;
class App extends Component {
state = {
isAuthenticated: false,
isAuthenticating: true,
user: null
}
setAuthStatus = authenticated =>{
this.setState({isAuthenticated: authenticated})
}
setUser = user =>{
this.setState({ user: user})
}
handleLogout = async () =>{
try{
Auth.signOut();
this.setAuthStatus(false);
this.setUser(null)
localStorage.removeItem('jwtToken')
localStorage.removeItem('idToken')
this.props.history.push('/')
}catch(error){
console.log(error)
}
}
tokenRefresh(){
const poolData = {
UserPoolId : config.cognito.USER_POOL_ID, // Your user pool id here,
ClientId : config.cognito.APP_CLIENT_ID// Your client id here
};
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
const cognitoUser = userPool.getCurrentUser();
cognitoUser.getSession((err, session) =>{
const refresh_token = session.getRefreshToken();
cognitoUser.refreshSession(refresh_token, (refErr, refSession) => {
if (refErr) {
throw refErr;
}
else{
localStorage.setItem('jwtToken',refSession.idToken.jwtToken)
localStorage.setItem('idToken',JSON.stringify(refSession.idToken))
}
});
})
}
async componentDidMount(){
try{
const session = await Auth.currentSession();
this.setAuthStatus(true);
const user = await Auth.currentAuthenticatedUser();
this.setUser(user);
}catch(error){
console.log(error);
}
// check if the token need refresh
this.setState({isAuthenticating: false})
let getIdToken = localStorage.getItem('idToken');
if(getIdToken !== null){
let newDateTime = new Date().getTime()/1000;
const newTime = Math.trunc(newDateTime);
const splitToken = getIdToken.split(".");
const decodeToken = atob(splitToken[1]);
const tokenObj = JSON.parse(decodeToken);
const newTimeMin = ((newTime) + (5 * 60)); //adding 5min faster from current time
if(newTimeMin > tokenObj.exp){
this.tokenRefresh();
}
}
}
render(){
const authProps = {
isAuthenticated: this.state.isAuthenticated,
user: this.state.user,
setAuthStatus: this.setAuthStatus,
setUser: this.setUser
}
return (
!this.state.isAuthenticating &&
<React.Fragment>
{this.state.isAuthenticated ?
<Drawer props={this.props} auth={authProps} handleLogout={this.handleLogout} onThemeChange={this.props.onThemeChange} /> :
<Switch>
<Redirect exact from='/' to='/login'/>
<Route path='/login' render={(props)=> <Login {...props} auth={authProps}/>} />
</Switch>
}
</React.Fragment>
);
}
}
export default withRouter(App);
Login.js
import React, { useState } from 'react';
import TextField from '@material-ui/core/TextField';
import withStyles from '@material-ui/core/styles/withStyles';
import _ from 'lodash';
import { Auth } from "aws-amplify";
function Login(props) {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (event) => {
event.preventDefault();
const payload = {
"username": username,
"password": password
}
// aws login
try{
const signInResponse = await Auth.signIn(payload.username,payload.password)
console.log(signInResponse)
props.history.push("/home")
props.auth.setAuthStatus(true)
props.auth.setUser(signInResponse)
localStorage.setItem('jwtToken',signInResponse.signInUserSession.idToken.jwtToken)
localStorage.setItem('idToken',JSON.stringify(signInResponse.signInUserSession.idToken))
}catch(error){
console.log(error)
}
}
return(
<form onSubmit={handleSubmit}>
<TextField
name='username'
value="username"
...
/>
<TextField
name='password'
value="password"
...
/>
</form>
);
}
export default withStyles(styles)(Login);
Upvotes: 1
Views: 7521
Reputation: 594
I built an app with the tutorial here: https://aws.amazon.com/getting-started/hands-on/build-serverless-web-app-lambda-apigateway-s3-dynamodb-cognito.
In the example application provided at s3://wildrydes-us-east-1/WebApplication/1_StaticWebHosting/website there is a file cognito-auth.js. In that file there is:
YourApp.authToken = new Promise(function fetchCurrentAuthToken(resolve, reject) {…
This does not refresh the auth token until the entire page is reloaded. I ran into issues when the page was loaded for a long time (hours) and only ajax calls were made. After the auth token that was loaded at the page load expired, the authorized ajax calls to my API returned 401 errors.
To resolve this, I added an async function wrapper to the promise:
YourApp.authToken = async function () {
return await new Promise(function fetchCurrentAuthToken(resolve, reject) {…
Then changed the app references from
YourApp.authToken.then(function (token) {…
to
YourApp.authToken().then(function (token) {…
Upvotes: 1
Reputation: 516
Why do you want to refresh token yourself as AWS Amplify handle it for you?
The documentation states that:
When using Authentication with AWS Amplify, you don’t need to refresh Amazon Cognito tokens manually. The tokens are automatically refreshed by the library when necessary.
Amplify automatically tries to refresh if the access token has timed out (which happens after an hour). You configure the refresh token expiration in the Cognito User Pools console.
import { Auth } from 'aws-amplify';
Auth.currentSession()
.then(data => console.log(data))
.catch(err => console.log(err));
Auth.currentSession()
returns a CognitoUserSession
object which contains JWT accessToken
, idToken
, and refreshToken
.
This method will automatically refresh the accessToken
and idToken
if tokens are expired and a valid refreshToken
presented. So you can use this method to refresh the session if needed.
https://docs.amplify.aws/lib/auth/manageusers/q/platform/js#managing-security-tokens https://docs.amplify.aws/lib/auth/manageusers/q/platform/js#retrieve-current-session
Upvotes: 2