Authenticate React App With Stormpath - Part Two
Clint Maruti
Posted on October 17, 2019
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>
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
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>
);
}
}
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>
);
}
}
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>
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>
);
}
}
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>
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>
);
}
}
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} />
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>
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>
);
}
}
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} />
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>
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>
);
}
}
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();
}
});
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>
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} />
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>
into:
<HomeRoute path='/' component={MasterPage}>
...
</HomeRoute>
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} />
So that it looks like:
<AuthenticatedRoute>
<HomeRoute path='/profile' component={ProfilePage} />
</AuthenticatedRoute>
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>
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>
);
}
}
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'
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';
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
And if everything is running successfully you should be able to see this message:
Listening at http://localhost:3000
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!
Posted on October 17, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.