Authenticate React App With Stormpath - Part Two

clintdev

Clint Maruti

Posted on October 17, 2019

Authenticate React App With Stormpath - Part Two

This is a continuation of Authenticate React App With Stormpath.

On this final piece, we'll set up our pages. Let's dive right in:-

Main Page

Let's first set up our Router which will define how our navigation structure in the app. We'll do this by first creating a shared route. This will act as our main page i.e. all routes under this page will share the same main component (header). Insert this code inside the <Router> tag in app.js.

<Router history={browserHistory}>
  <Route path='/' component={MasterPage}>
  </Route>
</Router>
Enter fullscreen mode Exit fullscreen mode

We have referenced MasterPage, something that doesn't exist yet. Let's go ahead and create it in a new directore pages, inside our src folder.

$ mkdir pages
$ cd pages
Enter fullscreen mode Exit fullscreen mode

Create a new file named masterPage.js and add this code:

import React from 'react';
import { Link } from 'react-router';
import { LoginLink } from 'react-stormpath';
import DocumentTitle from 'react-document-title';

import Header from './Header';

export default class is extends React.Component {
  render() {
    return (
      <DocumentTitle title='My React App'>
        <div className='MasterPage'>
          <Header />
          { this.props.children }
        </div>
      </DocumentTitle>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, we don't have a Header component yet, so let's go and create a new file named Header.js in the same directory with the following content.

import React from 'react';
import { Link } from 'react-router';
import { LoginLink, LogoutLink, Authenticated, NotAuthenticated } from 'react-stormpath';

export default class Header extends React.Component {
  render() {
    return (
      <nav className="navbar navbar-default navbar-static-top">
        <div className="container">
          <div id="navbar-collapse" className="collapse navbar-collapse">
            <ul className="nav navbar-nav">
              <li><Link to="/">Home</Link></li>
            </ul>
            <ul className="nav navbar-nav navbar-right">
            </ul>
          </div>
        </div>
      </nav>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Index Page

In our MainPage notice the property this.props.children. This will contain the components of the child routes that our router match. So if we had a route that looked like:

<Route path='/' component={MasterPage}>
  <Route path='/hello' component={HelloPage} />
</Route>
Enter fullscreen mode Exit fullscreen mode

And we tried to access /hello. The this.props.children array would be populated with a HelloPage component and for that reason, that component would be rendered on our master page.

Now imagine the scenario where you try to access /. Without any this.props.children, this would only render your master page but with empty content. This is where IndexRoute comes into play. With an IndexRoute you can specify the component that should be rendered when you hit the path of the master page route (in our case /).

But before we add our IndexRoute to our router, let's create a new file in our pages directory named IndexPage.js and add the following to it.

import { Link } from 'react-router';
import React, { PropTypes } from 'react';
import { LoginLink } from 'react-stormpath';

export default class IndexPage extends React.Component {
  render() {
    return (
      <div className="container">
        <h2 className="text-center">Welcome!</h2>
        <hr />
        <div className="jumbotron">
          <p>
            <strong>To my React application!</strong>
          </p>
          <p>Ready to begin? Try these Stormpath features that are included in this example:</p>
          <ol className="lead">
            <li><Link to="/register">Registration</Link></li>
            <li><LoginLink /></li>
            <li><Link to="/profile">Custom Profile Data</Link></li>
          </ol>
        </div>
      </div>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Now let's add our IndexRoute. Open up app.js and inside the tag <Route path='/' component={MasterPage}> add your IndexRoute so that it looks like the following:

<Route path='/' component={MasterPage}>
  <IndexRoute component={IndexPage} />
</Route>
Enter fullscreen mode Exit fullscreen mode

Login Page

We now have an application that shows a header with a default page. But we don't have any place to login yet. So let's create a new file named LoginPage.js and add some content to it:

import React from 'react';
import DocumentTitle from 'react-document-title';
import { LoginForm } from 'react-stormpath';

export default class LoginPage extends React.Component {
  render() {
    return (
      <DocumentTitle title={`Login`}>
        <div className="container">
          <div className="row">
            <div className="col-xs-12">
              <h3>Login</h3>
              <hr />
            </div>
          </div>
          <LoginForm />
        </div>
      </DocumentTitle>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice the LoginForm component. This is all we have to add in order for us to have a fully working form in which people can sign up from.

But before we can use it, we need to open up app.js and add a route for the page in our router. So inside the tag <Route path='/' component={MasterPage}> add the following:

<LoginRoute path='/login' component={LoginPage} />
Enter fullscreen mode Exit fullscreen mode

In order to be able to access the login page, we need to add this to our menu. So go ahead and open up Header.js and inside the element <ul className="nav navbar-nav navbar-right"> add the following:

<NotAuthenticated>
  <li>
    <LoginLink />
  </li>
</NotAuthenticated>
Enter fullscreen mode Exit fullscreen mode

As you can see we're using the NotAuthenticated component. With this we'll only show a LoginLink when the user isn't logged in yet.

Registration Page

Now, let's add a page where people can sign up. We'll call it RegistrationPage. So create a new file named RegistrationPage.js and put the following content in it:

import React from 'react';
import DocumentTitle from 'react-document-title';
import { RegistrationForm } from 'react-stormpath';

export default class RegistrationPage extends React.Component {
  render() {
    return (
      <DocumentTitle title={`Registration`}>
        <div className="container">
          <div className="row">
            <div className="col-xs-12">
              <h3>Registration</h3>
              <hr />
            </div>
          </div>
          <RegistrationForm />
        </div>
      </DocumentTitle>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice that we used the RegistrationForm component. As you might have guessed, this will render a Stormpath registration form. And once you've signed up it will point users to the login page where they'll be able to login.

In order to access this page. We need to add a route. So go ahead and open up app.js and inside the tag <Route path='/' component={MasterPage}> add:

<Route path='/register' component={RegistrationPage} />
Enter fullscreen mode Exit fullscreen mode

We now have a route, but people won't be able to find the page unless we link to it, so open up Header.js and add the following right before the closing tag (</ul>) of <ul className="nav navbar-nav navbar-right">:

<NotAuthenticated>
  <li>
    <Link to="/register">Create Account</Link>
  </li>
</NotAuthenticated>
Enter fullscreen mode Exit fullscreen mode

Notice the use of the NotAuthenticated component. With this we'll only show the /register link when the user isn't logged in.

Profile Page

Once a user is logged in, we want to be able to show them some personalized content (their user data). So create a new file named ProfilePage.js and put the following code in it:

import React from 'react';
import DocumentTitle from 'react-document-title';
import { UserProfileForm } from 'react-stormpath';

export default class ProfilePage extends React.Component {
  render() {
    return (
      <DocumentTitle title={`My Profile`}>
      <div className="container">
          <div className="row">
            <div className="col-xs-12">
              <h3>My Profile</h3>
              <hr />
            </div>
          </div>
          <div className="row">
            <div className="col-xs-12">
              <UserProfileForm />
            </div>
          </div>
        </div>
      </DocumentTitle>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice that we use the UserProfileForm. This is a simple helper form that allows you to edit the most basic user fields.

Though, in order to actually modify the user profile, we need to change a few things in our server. So open up server.js, add var bodyParser = require('body-parser'); to the top of the file and then add the following route underneath app.use(stormpath.init(app, ...));:

app.post('/me', bodyParser.json(), stormpath.loginRequired, function (req, res) {
  function writeError(message) {
    res.status(400);
    res.json({ message: message, status: 400 });
    res.end();
  }

  function saveAccount () {
    req.user.givenName = req.body.givenName;
    req.user.surname = req.body.surname;
    req.user.email = req.body.email;

    req.user.save(function (err) {
      if (err) {
        return writeError(err.userMessage || err.message);
      }
      res.end();
    });
  }

  if (req.body.password) {
    var application = req.app.get('stormpathApplication');

    application.authenticateAccount({
      username: req.user.username,
      password: req.body.existingPassword
    }, function (err) {
      if (err) {
        return writeError('The existing password that you entered was incorrect.');
      }

      req.user.password = req.body.password;

      saveAccount();
    });
  } else {
    saveAccount();
  }
});
Enter fullscreen mode Exit fullscreen mode

This will allow the form to change both the given name, surname, email and password of user. If you have additional fields that you wish to edit, then simply customize the UserProfileForm form and add the fields that you wish to edit in the route above.

Now, in order for us to access this page from the menu, open up Header.js and right below <li><Link to="/">Home</Link></li> add:

<Authenticated>
  <li>
    <Link to="/profile">Profile</Link>
  </li>
</Authenticated>
Enter fullscreen mode Exit fullscreen mode

With this, using the Authenticated "https://github.com/stormpath/stormpath-sdk-react/blob/master/docs/api.md#authenticated) component, when we have a user session we'll render a link to the /profile page and allow our users to view their user profile.

In order for us to be able to access the page, we must as with the other pages add it to the router. Open up app.js and inside the tag <Route path='/' component={MasterPage}> add:

<AuthenticatedRoute path='/profile' component={ProfilePage} />
Enter fullscreen mode Exit fullscreen mode

Notice that we're using AuthenticatedRoute. This is a route that can only be accessed if there is an authenticated user session. If there's no session, then the user will automatically be redirected to the path of the LoginLink.

Home Route

Now when we've setup most of our routing. Let's look at a special route called the HomeRoute. This route itself doesn't do anything. But acts as a "marker", to indicate where to redirect to when logging in and logging out.

So in order to specify where we want to end up when we log out, open up app.js and change the:

<Route path='/' component={MasterPage}>
  ...
</Route>
Enter fullscreen mode Exit fullscreen mode

into:

<HomeRoute path='/' component={MasterPage}>
  ...
</HomeRoute>
Enter fullscreen mode Exit fullscreen mode

Now when logging out, the Stormpath SDK will know that it should redirect to the '/' path. Now, to specify where to redirect when logging out, change the AuthenticatedRoute that we created in the previous step:

<AuthenticatedRoute path='/profile' component={ProfilePage} />
Enter fullscreen mode Exit fullscreen mode

So that it looks like:

<AuthenticatedRoute>
  <HomeRoute path='/profile' component={ProfilePage} />
</AuthenticatedRoute>
Enter fullscreen mode Exit fullscreen mode

Notice how the AuthenticatedRoute wraps the HomeRoute. This is used to indicate the authenticated route that we want to redirect to after login.

Logout

Finally, once our users have signed up and logged in. We want to give them the option to logout. Fortunately, adding this is really simple.

So open up Header.js and inside <ul className="nav navbar-nav navbar-right"> add this code to the end:

<Authenticated>
  <li>
    <LogoutLink />
  </li>
</Authenticated>
Enter fullscreen mode Exit fullscreen mode

Notice the LogoutLink component. Once this is clicked, the user session will be automatically destroyed and the user will be redirected to the unauthenticated HomeRoute.

User State in Components

Access user state in your components by requesting the authenticated and user context types:

class ContextExample extends React.Component {
  static contextTypes = {
    authenticated: React.PropTypes.bool,
    user: React.PropTypes.object
  };

  render() {
    if (!this.context.authenticated) {
      return (
        <div>
          You need to <LoginLink />.
        </div>
      );
    }

    return (
      <div>
        Welcome {this.context.user.username}!
      </div>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Import Components

To be able to reference our pages we need to import them. And in order to make importing easy, we'll put them all together in an index.js file so we only have to import it once. So let's create a new file named index.js in our pages directory and export all of our pages from it, as shown below:

export MasterPage from './MasterPage'
export IndexPage from './IndexPage'
export LoginPage from './LoginPage'
export RegistrationPage from './RegistrationPage'
export ProfilePage from './ProfilePage'
Enter fullscreen mode Exit fullscreen mode

With this, we'll only have to do one import in order to have access to all of our pages.

So let's do that. Open up app.js file and at the top of the file, add the following import statement:

import { MasterPage, IndexPage, LoginPage, RegistrationPage, ProfilePage } from './pages';
Enter fullscreen mode Exit fullscreen mode

Run The Project

Now we have an application where our users can sign up, login, and show their user data. So let's try it out!

As before, start our server by running the following:

$ node server.js
Enter fullscreen mode Exit fullscreen mode

And if everything is running successfully you should be able to see this message:

Listening at http://localhost:3000
Enter fullscreen mode Exit fullscreen mode

So, open up http://localhost:3000 in your browser and try it out!

Wrapping Up

As you have seen in this tutorial, React is a really powerful tool and when used together with ES6, JSX and Stormpath, building apps suddenly becomes fun again.

If you have questions regarding the Stormpath React SDK, be sure to check out its API documentation.

Happy Hacking!

💖 💪 🙅 🚩
clintdev
Clint Maruti

Posted on October 17, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related