built web-app Authentication in React Applications
Authentication using an email address
before that we setup our react environment
We can install these packages by typing
npm install --save bcrypt jsonwebtoken mongoose passport passport-local
- bcrypt -- algorithm implementation for hashing passwords
- jsonwebtoken -- JSON Web Token standard
- mongoose -- MongoDB ORM library
- passport --flexible authentication library
- passport-local --Passport strategy for authenticating with an email and a password
and we create a root folder structure like this
install the package.json -------
by typing/ npm init
package.json
looked like this: {
Project Directory Structure
Authentication Logic
Let’s update application file:
index.js
we will save a database connection string
config/index.json
{
"dbUri": "mongodb://localhost/react_app",
"jwtSecret": "a secret phrase!!"
}
As it’s on my local machine I don’t use a password to access the database. If you have to provide an username and a password use this format:
Now let’s create a module with the database connection:
server/models/index.js
The next step is the creation of the authenticaion checker:
server/middleware/auth-check.js
const jwt = require('jsonwebtoken');
const User = require('mongoose').model('User');
const config = require('../../config');
/**
* The Auth Checker middleware function.
*/
module.exports = (req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).end();
}
// get the last part from a authorization header string like "bearer token-value"
const token = req.headers.authorization.split(' ')[1];
// decode the token using a secret key-phrase
return jwt.verify(token, config.jwtSecret, (err, decoded) => {
// the 401 code is for unauthorized status
if (err) { return res.status(401).end(); }
const userId = decoded.sub;
// check if a user exists
return User.findById(userId, (userErr, user) => {
if (userErr || !user) {
return res.status(401).end();
}
return next();
});
});
};
Now we need to add calls of the Passport strategy functions in /auth routes handler.
server/routes/auth.js
const express = require('express');
const validator = require('validator');
const passport = require('passport');
const router = new express.Router();
/**
* Validate the sign up form
*
* @param {object} payload - the HTTP body message
* @returns {object} The result of validation. Object contains a boolean validation result,
* errors tips, and a global message for the whole form.
*/
function validateSignupForm(payload) {
const errors = {};
let isFormValid = true;
let message = '';
if (!payload || typeof payload.email !== 'string' || !validator.isEmail(payload.email)) {
isFormValid = false;
errors.email = 'Please provide a correct email address.';
}
if (!payload || typeof payload.password !== 'string' || payload.password.trim().length < 8) {
isFormValid = false;
errors.password = 'Password must have at least 8 characters.';
}
if (!payload || typeof payload.name !== 'string' || payload.name.trim().length === 0) {
isFormValid = false;
errors.name = 'Please provide your name.';
}
if (!isFormValid) {
message = 'Check the form for errors.';
}
return {
success: isFormValid,
message,
errors
};
}
/**
* Validate the login form
*
* @param {object} payload - the HTTP body message
* @returns {object} The result of validation. Object contains a boolean validation result,
* errors tips, and a global message for the whole form.
*/
function validateLoginForm(payload) {
const errors = {};
let isFormValid = true;
let message = '';
if (!payload || typeof payload.email !== 'string' || payload.email.trim().length === 0) {
isFormValid = false;
errors.email = 'Please provide your email address.';
}
if (!payload || typeof payload.password !== 'string' || payload.password.trim().length === 0) {
isFormValid = false;
errors.password = 'Please provide your password.';
}
if (!isFormValid) {
message = 'Check the form for errors.';
}
return {
success: isFormValid,
message,
errors
};
}
router.post('/signup', (req, res, next) => {
const validationResult = validateSignupForm(req.body);
if (!validationResult.success) {
return res.status(400).json({
success: false,
message: validationResult.message,
errors: validationResult.errors
});
}
return passport.authenticate('local-signup', (err) => {
if (err) {
if (err.name === 'MongoError' && err.code === 11000) {
// the 11000 Mongo code is for a duplication email error
// the 409 HTTP status code is for conflict error
return res.status(409).json({
success: false,
message: 'Check the form for errors.',
errors: {
email: 'This email is already taken.'
}
});
}
return res.status(400).json({
success: false,
message: 'Could not process the form.'
});
}
return res.status(200).json({
success: true,
message: 'You have successfully signed up! Now you should be able to log in.'
});
})(req, res, next);
});
router.post('/login', (req, res, next) => {
const validationResult = validateLoginForm(req.body);
if (!validationResult.success) {
return res.status(400).json({
success: false,
message: validationResult.message,
errors: validationResult.errors
});
}
return passport.authenticate('local-login', (err, token, userData) => {
if (err) {
if (err.name === 'IncorrectCredentialsError') {
return res.status(400).json({
success: false,
message: err.message
});
}
return res.status(400).json({
success: false,
message: 'Could not process the form.'
});
}
return res.json({
success: true,
message: 'You have successfully logged in!',
token,
user: userData
});
})(req, res, next);
});
module.exports = router;
server/routes/api.js
const express = require('express');
const router = new express.Router();
router.get('/dashboard', (req, res) => {
res.status(200).json({
message: "You're authorized to see this secret message."
});
});
module.exports = router;
client/src/app.jsx
import React from 'react';
import ReactDom from 'react-dom';
import injectTapEventPlugin from 'react-tap-event-plugin';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import { browserHistory, Router } from 'react-router';
import routes from './routes.js';
// remove tap delay, essential for MaterialUI to work properly
injectTapEventPlugin();
ReactDom.render((
<MuiThemeProvider muiTheme={getMuiTheme()}>
<Router history={browserHistory} routes={routes} />
</MuiThemeProvider>), document.getElementById('react-app'));
client/src/routes.js
import Base from './components/Base.jsx';
import HomePage from './components/HomePage.jsx';
import DashboardPage from './containers/DashboardPage.jsx';
import LoginPage from './containers/LoginPage.jsx';
import SignUpPage from './containers/SignUpPage.jsx';
import Auth from './modules/Auth';
const routes = {
// base component (wrapper for the whole application).
component: Base,
childRoutes: [
{
path: '/',
getComponent: (location, callback) => {
if (Auth.isUserAuthenticated()) {
callback(null, DashboardPage);
} else {
callback(null, HomePage);
}
}
},
{
path: '/login',
component: LoginPage
},
{
path: '/signup',
component: SignUpPage
},
{
path: '/logout',
onEnter: (nextState, replace) => {
Auth.deauthenticateUser();
// change the current URL to /
replace('/');
}
}
]
};
export default routes;
client/src/modules/Auth.js
class Auth {
/**
* Authenticate a user. Save a token string in Local Storage
*
* @param {string} token
*/
static authenticateUser(token) {
localStorage.setItem('token', token);
}
/**
* Check if a user is authenticated - check if a token is saved in Local Storage
*
* @returns {boolean}
*/
static isUserAuthenticated() {
return localStorage.getItem('token') !== null;
}
/**
* Deauthenticate a user. Remove a token from Local Storage.
*
*/
static deauthenticateUser() {
localStorage.removeItem('token');
}
/**
* Get a token value.
*
* @returns {string}
*/
static getToken() {
return localStorage.getItem('token');
}
}
export default Auth;
The updated Base component:
client/src/components/Base.jsx
import React, { PropTypes } from 'react';
import { Link, IndexLink } from 'react-router';
import Auth from '../modules/Auth';
const Base = ({ children }) => (
<div>
<div className="top-bar">
<div className="top-bar-left">
<IndexLink to="/">React App</IndexLink>
</div>
{Auth.isUserAuthenticated() ? (
<div className="top-bar-right">
<Link to="/logout">Log out</Link>
</div>
) : (
<div className="top-bar-right">
<Link to="/login">Log in</Link>
<Link to="/signup">Sign up</Link>
</div>
)}
</div>
{ /* child component will be rendered here */ }
{children}
</div>
);
Base.propTypes = {
children: PropTypes.object.isRequired
};
export default Base;
Now we need to add a redirect to the login form page after successful registration:
client/src/containers/SignUpPage.jsx
import React, { PropTypes } from 'react';
import SignUpForm from '../components/SignUpForm.jsx';
class SignUpPage extends React.Component {
/**
* Class constructor.
*/
constructor(props, context) {
super(props, context);
// set the initial component state
this.state = {
errors: {},
user: {
email: '',
name: '',
password: ''
}
};
this.processForm = this.processForm.bind(this);
this.changeUser = this.changeUser.bind(this);
}
/**
* Process the form.
*
* @param {object} event - the JavaScript event object
*/
processForm(event) {
// prevent default action. in this case, action is the form submission event
event.preventDefault();
// create a string for an HTTP body message
const name = encodeURIComponent(this.state.user.name);
const email = encodeURIComponent(this.state.user.email);
const password = encodeURIComponent(this.state.user.password);
const formData = `name=${name}&email=${email}&password=${password}`;
// create an AJAX request
const xhr = new XMLHttpRequest();
xhr.open('post', '/auth/signup');
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.responseType = 'json';
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
// success
// change the component-container state
this.setState({
errors: {}
});
// set a message
localStorage.setItem('successMessage', xhr.response.message);
// make a redirect
this.context.router.replace('/login');
} else {
// failure
const errors = xhr.response.errors ? xhr.response.errors : {};
errors.summary = xhr.response.message;
this.setState({
errors
});
}
});
xhr.send(formData);
}
/**
* Change the user object.
*
* @param {object} event - the JavaScript event object
*/
changeUser(event) {
const field = event.target.name;
const user = this.state.user;
user[field] = event.target.value;
this.setState({
user
});
}
/**
* Render the component.
*/
render() {
return (
<SignUpForm
onSubmit={this.processForm}
onChange={this.changeUser}
errors={this.state.errors}
user={this.state.user}
/>
);
}
}
SignUpPage.contextTypes = {
router: PropTypes.object.isRequired
};xport default SignUpPage;
client/src/components/SignUpForm.jsx
import React, { PropTypes } from 'react';
import { Link } from 'react-router';
import { Card, CardText } from 'material-ui/Card';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';
const SignUpForm = ({
onSubmit,
onChange,
errors,
user,
}) => (
<Card className="container">
<form action="/" onSubmit={onSubmit}>
<h2 className="card-heading">Sign Up</h2>
{errors.summary && <p className="error-message">{errors.summary}</p>}
<div className="field-line">
<TextField
floatingLabelText="Name"
name="name"
errorText={errors.name}
onChange={onChange}
value={user.name}
/>
</div>
<div className="field-line">
<TextField
floatingLabelText="Email"
name="email"
errorText={errors.email}
onChange={onChange}
value={user.email}
/>
</div>
<div className="field-line">
<TextField
floatingLabelText="Password"
type="password"
name="password"
onChange={onChange}
errorText={errors.password}
value={user.password}
/>
</div>
<div className="button-line">
<RaisedButton type="submit" label="Create New Account" primary />
</div>
<CardText>Already have an account? <Link to={'/login'}>Log in</Link></CardText>
</form>
</Card>
);
SignUpForm.propTypes = {
onSubmit: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
errors: PropTypes.object.isRequired,
user: PropTypes.object.isRequired
};
export default SignUpForm;
Let’s update the login page container component:
client/src/containers/LoginPage.jsx
import React, { PropTypes } from 'react';
import Auth from '../modules/Auth';
import LoginForm from '../components/LoginForm.jsx';
class LoginPage extends React.Component {
/**
* Class constructor.
*/
constructor(props, context) {
super(props, context);
const storedMessage = localStorage.getItem('successMessage');
let successMessage = '';
if (storedMessage) {
successMessage = storedMessage;
localStorage.removeItem('successMessage');
}
// set the initial component state
this.state = {
errors: {},
successMessage,
user: {
email: '',
password: ''
}
};
this.processForm = this.processForm.bind(this);
this.changeUser = this.changeUser.bind(this);
}
/**
* Process the form.
*
* @param {object} event - the JavaScript event object
*/
processForm(event) {
// prevent default action. in this case, action is the form submission event
event.preventDefault();
// create a string for an HTTP body message
const email = encodeURIComponent(this.state.user.email);
const password = encodeURIComponent(this.state.user.password);
const formData = `email=${email}&password=${password}`;
// create an AJAX request
const xhr = new XMLHttpRequest();
xhr.open('post', '/auth/login');
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.responseType = 'json';
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
// success
// change the component-container state
this.setState({
errors: {}
});
// save the token
Auth.authenticateUser(xhr.response.token);
// change the current URL to /
this.context.router.replace('/');
} else {
// failure
// change the component state
const errors = xhr.response.errors ? xhr.response.errors : {};
errors.summary = xhr.response.message;
this.setState({
errors
});
}
});
xhr.send(formData);
}
/**
* Change the user object.
*
* @param {object} event - the JavaScript event object
*/
changeUser(event) {
const field = event.target.name;
const user = this.state.user;
user[field] = event.target.value;
this.setState({
user
});
}
/**
* Render the component.
*/
render() {
return (
<LoginForm
onSubmit={this.processForm}
onChange={this.changeUser}
errors={this.state.errors}
successMessage={this.state.successMessage}
user={this.state.user}
/>
);
}
}
LoginPage.contextTypes = {
router: PropTypes.object.isRequired
};
export default LoginPage;
client/src/components/LoginForm.jsx
import React, { PropTypes } from 'react';
import { Link } from 'react-router';
import { Card, CardText } from 'material-ui/Card';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';
const LoginForm = ({
onSubmit,
onChange,
errors,
successMessage,
user
}) => (
<Card className="container">
<form action="/" onSubmit={onSubmit}>
<h2 className="card-heading">Login</h2>
{successMessage && <p className="success-message">{successMessage}</p>}
{errors.summary && <p className="error-message">{errors.summary}</p>}
<div className="field-line">
<TextField
floatingLabelText="Email"
name="email"
errorText={errors.email}
onChange={onChange}
value={user.email}
/>
</div>
<div className="field-line">
<TextField
floatingLabelText="Password"
type="password"
name="password"
onChange={onChange}
errorText={errors.password}
value={user.password}
/>
</div>
<div className="button-line">
<RaisedButton type="submit" label="Log in" primary />
</div>
<CardText>Don't have an account? <Link to={'/signup'}>Create one</Link>.</CardText>
</form>
</Card>
);
LoginForm.propTypes = {
onSubmit: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
errors: PropTypes.object.isRequired,
successMessage: PropTypes.string.isRequired,
user: PropTypes.object.isRequired
};
export default LoginForm;
client/src/containers/DashboardPage.jsx
import React from 'react';
import Auth from '../modules/Auth';
import Dashboard from '../components/Dashboard.jsx';
class DashboardPage extends React.Component {
/**
* Class constructor.
*/
constructor(props) {
super(props);
this.state = {
secretData: ''
};
}
/**
* This method will be executed after initial rendering.
*/
componentDidMount() {
const xhr = new XMLHttpRequest();
xhr.open('get', '/api/dashboard');
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
// set the authorization HTTP header
xhr.setRequestHeader('Authorization', `bearer ${Auth.getToken()}`);
xhr.responseType = 'json';
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
this.setState({
secretData: xhr.response.message
});
}
});
xhr.send();
}
/**
* Render the component.
*/
render() {
return (<Dashboard secretData={this.state.secretData} />);
}
}
export default DashboardPage;
don't forget to typing
npm run bundle
and npm start
Go to
http://localhost:3000
in a browser and go the sign-up form. Fill the fields:
finnally i made it Authentication in React Applications
homepage |
onces you sign up after you ready to ligin |
if your password or email id is incorrect you can't access it |
amazingyou sucessfully login your dashboard |
Comments