1

I have a SPA react application where I am using auth0 for authentication. I would like to do a silent authentication and get a new token whenever site is refreshed, like it is suggested in this answer. I have an Auth class responsible for handling tokens:

import auth0 from 'auth0-js'
import { authConfig } from '../config'

export default class Auth {
  accessToken
  idToken
  expiresAt

  auth0 = new auth0.WebAuth({
    domain: authConfig.domain,
    clientID: authConfig.clientId,
    redirectUri: authConfig.callbackUrl,
    responseType: 'token id_token',
    scope: 'openid'
  })

  constructor(history) {
    this.history = history

    this.login = this.login.bind(this)
    this.logout = this.logout.bind(this)
    this.handleAuthentication = this.handleAuthentication.bind(this)
    this.isAuthenticated = this.isAuthenticated.bind(this)
    this.getAccessToken = this.getAccessToken.bind(this)
    this.getIdToken = this.getIdToken.bind(this)
    this.renewSession = this.renewSession.bind(this)
  }

  login() {
    this.auth0.authorize()
  }

  handleAuthentication() {
    this.auth0.parseHash((err, authResult) => {
      if (authResult && authResult.accessToken && authResult.idToken) {
        console.log('Access token: ', authResult.accessToken)
        console.log('id token: ', authResult.idToken)
        this.setSession(authResult)
      } else if (err) {
        this.history.replace('/')
        console.log(err)
        alert(`Error: ${err.error}. Check the console for further details.`)
      }
    })
  }

  getAccessToken() {
    return this.accessToken
  }

  getIdToken() {
    return this.idToken
  }

  setSession(authResult) {
    // Set isLoggedIn flag in localStorage
    localStorage.setItem('isLoggedIn', 'true')

    // Set the time that the access token will expire at
    let expiresAt = authResult.expiresIn * 1000 + new Date().getTime()
    this.accessToken = authResult.accessToken
    this.idToken = authResult.idToken
    this.expiresAt = expiresAt

    // navigate to the home route
    this.history.replace('/')
  }

  renewSession(cb) {
    this.auth0.checkSession({}, (err, authResult) => {
      if (authResult && authResult.accessToken && authResult.idToken) {
        this.setSession(authResult)
        cb(err, authResult)
      } else if (err) {
        this.logout()
        console.log(
          `Could not get a new token (${err.error}: ${err.error_description}).`
        )
      }
    })
  }

  logout() {
    // Remove tokens and expiry time
    this.accessToken = null
    this.idToken = null
    this.expiresAt = 0

    // Remove isLoggedIn flag from localStorage
    localStorage.removeItem('isLoggedIn')

    this.auth0.logout({
      return_to: `${window.location.origin}/login`
    })
  }

  isAuthenticated() {
    // Check whether the current time is past the
    // access token's expiry time
    let expiresAt = this.expiresAt
    return new Date().getTime() < expiresAt
  }
}

In my main App component in componentDidMount I am calling the renewSession method from Auth class:

export default class App extends Component<AppProps, AppState> {
  constructor(props: AppProps) {
    super(props)

    this.handleLogin = this.handleLogin.bind(this)
    this.handleLogout = this.handleLogout.bind(this)
    this.createNewPost = this.createNewPost.bind(this)
  }

  state: AppState = {
    tokenRenewed: false
  }

  componentDidMount() {
    this.props.auth.renewSession(() => {
      this.setState({ tokenRenewed: true })
    })
  }

  handleLogin() {
    this.props.auth.login()
  }

  handleLogout() {
    this.props.auth.logout()
  }

  async createNewPost() {
    const idToken = this.props.auth.getIdToken()
    try {
      const newPost = await createPost(idToken)
      this.props.history.push(`/posts/${newPost.postId}/edit`)
    } catch {
      alert('Post creation failed')
    }
  }

  render() {
    if (!this.state.tokenRenewed) return 'loading...'
    const userAuthenticated = this.props.auth.isAuthenticated()
    return (
      <div>
        <Segment vertical>
          <Grid container stackable verticalAlign="middle">
            <Grid.Row>
              <Grid.Column width={16}>
                <Router history={this.props.history}>
                  {this.generateMenu(userAuthenticated)}
                  {this.generateCurrentPage(userAuthenticated)}
                </Router>
              </Grid.Column>
            </Grid.Row>
          </Grid>
        </Segment>
      </div>
    )
  }

And this are my routes:

<Router history={history}>
  <Switch>
    <Route
      path="/callback"
      render={props => {
        handleAuthentication(props)
        return <Callback />
      }}
    />
    <Route
      path="/login"
      render={props => {
        return <LogIn auth={auth} {...props} />
      }}
    />
    <Route
      path="/"
      render={props => {
        return <App auth={auth} {...props} />
      }}
    />
  </Switch>
</Router>

That works fine if I login with username/password. But, if I use a social login like Google/Gmail then whenever I login in to the app, I get an error from auth0.checkSession in Auth class:

Could not get a new token (login_required: Login required).

How can I make this work with Google/Gmail login as well?

Leff
  • 1,968
  • 24
  • 97
  • 201

0 Answers0