aws cognito - 如何在前端的正确时间保持 id 令牌刷新

问题:
每次登录时,Auth.signIn 获取的 id 令牌都会存储在 localStorage 中。

登录后,UI 发出需要授权的请求(使用 id 令牌),
但每次都失败了。
我尝试在 localStorage 中复制 id 令牌并在 Postman 中尝试相同的 API 请求,
下面显示的错误消息。

the incoming token has expired

但是当我重新加载页面时,请求已成功发送并收到 ok 响应。

我不确定是否是因为我的代码中的令牌刷新逻辑不正确。

我只是将令牌刷新逻辑放在 App.js 的 componentDidMount() 中。
逻辑基于以下帖子。
how handle refresh token service in AWS amplify-js

有人可以让我知道我的代码有什么问题吗?

索引.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();

应用程序.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);

登录.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);
stack overflow aws cognito - how to keep the id token refresh at the right time in frontend
原文答案

答案:

作者头像

为什么要在 AWS Amplify 为您处理令牌时自己刷新令牌?

该文件指出:

使用 AWS Amplify 进行身份验证时,您无需手动刷新 Amazon Cognito 令牌。必要时,库会自动刷新令牌。

如果访问令牌超时(一小时后发生),Amplify 会自动尝试刷新。您可以在 Cognito 用户池控制台中配置刷新令牌过期时间。

import { Auth } from 'aws-amplify';

Auth.currentSession()
  .then(data => console.log(data))
  .catch(err => console.log(err));

Auth.currentSession() 返回一个 CognitoUserSession 对象,其中包含 JWT accessTokenidTokenrefreshToken

如果令牌过期并且出现有效的 accessToken ,此方法将自动刷新 idTokenrefreshToken 。因此,如果需要,您可以使用此方法刷新会话。

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