1
votes

Hi guys I'm using Auth0 to handle my authentication in my ASP.NET React app. I want users to be able to log in then after they are taken to Auth0 to login then redirected back to the app with the token, render the app with componentDidMount to get the user info and put it on screen. Currently it only renders the information if I refresh the page.

Here's the my routes.jsx

let auth = new Auth();

const handleAuthentication = (props:any) => {
  if (/access_token|id_token|error/.test(props.location.hash)) {
    auth.handleAuthentication(props);
  }
}

export const routes = 
  <Layout auth={auth}>
    <Route exact path='/' render={(props) => <Home />} />
    <Route path='/counter' render={(props) => <Counter {...props} />} />
    <Route path='/fetchdata' render={(props) => <FetchData {...props} />} />
    <Route path="/callback" render={props => {
      handleAuthentication(props);
      return <Callback {...props} />
    }} />
  </Layout>;

Here's my Layout.jsx where I try to render the user info on componentDidMount:

export class Layout extends React.Component<LayoutProps, LayoutStates> {
  constructor(props: any) {
    super(props);
    this.state = {
      profile: null
    }
  }

  componentDidMount() {
    const { isAuthenticated, getProfile, userProfile } = this.props.auth;
    if (isAuthenticated()) {
      getProfile( (err: any, profile: any) => {
        this.setState({
          profile: profile
        })
      })
    }
  }

  public render() {
    const { profile } = this.state;
    return (
      <div className='container-fluid'>
        <div className='row'>
          <div className='col-sm-3'>
            <NavMenu auth={this.props.auth} />
          </div>
          <div className='col-sm-9'>
            <h1 style={{color: 'red'}}>{profile == null ? 'No User Found' : this.state.profile.name}</h1>
            {this.props.children}
          </div>
        </div>
      </div>);
  }
}

And here's my Auth.js service:

export default class Auth {
  private _auth0 = new auth0.WebAuth({
    domain: 'my_client_domain',
    clientID: 'some_client_id',
    redirectUri: 'http://localhost:5000/callback',
    audience: 'http://localhost:5000/api/',
    responseType: 'token id_token',
    scope: 'openid profile'
  })

  userProfile: any;

  constructor() {
    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.getProfile = this.getProfile.bind(this);
  }

  handleAuthentication(props:any) {
    this._auth0.parseHash((err: any, authResult: any) => {
      if (authResult && authResult.accessToken && authResult.idToken) {
        this.setSession(authResult);
        props.history.replace('/');
      } else if (err) {
        props.history.replace('/');
        console.log(err);
        return err;
      }
    })
  }

  getProfile(cb: any) {
    if (this.isAuthenticated()) {
      const token = String(localStorage.getItem('access_token'));
      this._auth0.client.userInfo(token, (err, profile) => {
        if(profile) {
          this.userProfile = profile;
          cb(err,profile);
        } else {
          console.log(profile);
        }
      })
    }
  }

  setSession(authResult: any) {
    let expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());
    localStorage.setItem('access_token', authResult.accessToken);
    localStorage.setItem('id_token', authResult.idToken);
    localStorage.setItem('expires_at', expiresAt);
    history.replace('/');
  }

  logout() {
    localStorage.removeItem('access_token');
    localStorage.removeItem('id_token');
    localStorage.removeItem('expires_at');
    this.userProfile = null;
    history.replace('/');
  }

  login() {
    this._auth0.authorize();
  }

  isAuthenticated() {
    let expiresAt = JSON.parse(String(localStorage.getItem('expires_at')));
    return new Date().getTime() < expiresAt;
  }
}

Any help is appreciated. Thanks.

1
componentDidMount will only be called once, so even if your props changed componentDidMount will not be executed. in short thats the wrong place to put your conditionRei Dien

1 Answers

1
votes

To expound upon Rei Dien's comment, what you want to do is use componentDidUpdate (you could potentially use another lifecycle method) instead of componentDidMount.

componentDidUpdate() {
  const { isAuthenticated, getProfile, userProfile } = this.props.auth;
  if (isAuthenticated()) {
    getProfile( (err: any, profile: any) => {
      this.setState({
        profile: profile
      })
    })
  }
}

This way, when your props change your lifecycle method will fire and your component will render like you expect. Depending on your application flow and whether or not a user can come directly to this page while logged in, you might still want to implement componentDidMount.

Perhaps like this:

componentDidMount() {
  this.loadProfile();
}

componentDidUpdate() {
  this.loadProfile();
}

loadProfile() {
   const { isAuthenticated, getProfile, userProfile } = this.props.auth;
   if (isAuthenticated()) {
    getProfile( (err: any, profile: any) => {
      this.setState({
        profile: profile
      })
    })
  }
}