In this big article, we will use Laravel and ReactJS to setup authentication system. We'll use Auth scaffolding that comes with Laravel 5.5. Anyways, before we start running through the code, it'll be a good idea to divide our goal, the authentication system, into smaller parts:
- Landing Page
- Sign In Page
- Create Account Page
- Dashboard Page
With the idea above, we can be sure that we'll have four components: SignInPage
, CreateAccountPage
, LandingPage
, DashboardPage
. And let's say these components will be shown on /signin
, /account/create
, /
and /dashboard
URLs respectively. Considering this, we can quickly predict how our router components will look like:
<BrowserRouter>
<div>
<Route exact path="/" component={LandingPage} />
<Route path="/signin" component={SignInPage} />
<Route path="/account/create" component={CreateAccountPage} />
<Route path="/dashboard" component={DashboardPage} />
</div>
</BrowserRouter>
Before you ask, the <div>
becomes necessary because <BrowserRouter>
accepts only one child. Continuing, let's make the components:
const LandingPage = (props) => {
return <div>Landing Page</div>;
};
const SignInPage = (props) => {
return <div>Sign In Page</div>;
};
const CreateAccountPage = (props) => {
return <div>Create Account Page</div>;
};
const DashboardPage = (props) => {
return <div>Dashboard Page</div>;
};
We've kept them simple; so far. Also, notice all of the four components are stateless. It's my little thing that I always start off with stateless components and then if needed make them stateful. Unless, ofcourse, you know already that your component will have state. Anyways, we move on.
If you haven't guessed it so far, all of the code above will go into our /resources/assets/js/components/App.js
file with a few adjustments, ofcourse. Let's see how App.js
should look like so far:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import {
BrowserRouter,
Route
} from 'react-router-dom';
let LandingPage = (props) => {
return <h1 className="mt-5">Landing Page</h1>;
};
let SignInPage = (props) => {
return <h1 className="mt-5">Sign In Page</h1>;
};
let CreateAccountPage = (props) => {
return <h1 className="mt-5">Create Account Page</h1>;
};
let DashboardPage = (props) => {
return <h1 className="mt-5">Dashboard Page</h1>;
};
class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<BrowserRouter>
<div>
<Route exact path="/" component={LandingPage} />
<Route path="/signin" component={SignInPage} />
<Route path="/account/create" component={CreateAccountPage} />
<Route path="/dashboard" component={DashboardPage} />
</div>
</BrowserRouter>
);
}
};
ReactDOM.render(<App />, document.getElementById('app'));
And yes, by principle, LandingPage
, SignInPage
, CreateAccountPage
, DashboardPage
should be in their own file and should be imported into App.js
but, for simplicity, we keep it as is. Next, we can make a Header
component that contains Link
components. So, let's import Link
from react-router-dom
and declare Header
as follows:
let Header = (props) => {
return (
<nav className="navbar navbar-expand-lg navbar-light bg-light">
<div className="container px-2 px-sm-3">
<Link className="navbar-brand" to="/">DEV.TO</Link>
<button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarSupportedContent">
<ul className="navbar-nav ml-auto">
<li className="nav-item">
<Link className="nav-link" to="/signin">Sign In</Link>
</li>
<li className="nav-item">
<Link className="btn btn-primary ml-lg-2 ml-sm-0 mt-2 mt-lg-0" to="/account/create">Create Account</Link>
</li>
</ul>
<ul className="navbar-nav ml-auto">
<li className="nav-item">
<Link className="btn btn-primary ml-lg-2 ml-sm-0 mt-2 mt-lg-0" to="/dashboard">Dashboard</Link>
</li>
</ul>
</div>
</div>
</nav>
);
};
I have kept the Dashboard link in a separate <ul>
because soon we will introduce a conditional if/else
in the form of ternary operator in our Header. But, let's keep running!
I desperately want to skip to SignInPage
component but, let's look at the App.js
file so far:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import {
BrowserRouter,
Link,
Route
} from 'react-router-dom';
let LandingPage = (props) => {
return <h1 className="mt-5">Landing Page</h1>;
};
let SignInPage = (props) => {
return <h1 className="mt-5">Sign In Page</h1>;
};
let CreateAccountPage = (props) => {
return <h1 className="mt-5">Create Account Page</h1>;
};
let DashboardPage = (props) => {
return <h1 className="mt-5">Dashboard Page</h1>;
};
let Header = (props) => {
return (
<nav className="navbar navbar-expand-lg navbar-light bg-light">
<div className="container px-2 px-sm-3">
<Link className="navbar-brand" to="/">DEV.TO</Link>
<button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarSupportedContent">
<ul className="navbar-nav ml-auto">
<li className="nav-item">
<Link className="nav-link" to="/signin">Sign In</Link>
</li>
<li className="nav-item">
<Link className="btn btn-primary ml-lg-2 ml-sm-0 mt-2 mt-lg-0" to="/account/create">Create Account</Link>
</li>
</ul>
<ul className="navbar-nav ml-auto">
<li className="nav-item">
<Link className="btn btn-primary ml-lg-2 ml-sm-0 mt-2 mt-lg-0" to="/dashboard">Dashboard</Link>
</li>
</ul>
</div>
</div>
</nav>
);
};
class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<BrowserRouter>
<div>
<Header />
<Route exact path="/" component={LandingPage} />
<Route path="/signin" component={SignInPage} />
<Route path="/account/create" component={CreateAccountPage} />
<Route path="/dashboard" component={DashboardPage} />
</div>
</BrowserRouter>
);
}
};
ReactDOM.render(<App />, document.getElementById('app'));
Now, SignInPage
is going to contain a form mainly. And, for the form management, we have included redux-form
which depends on redux
. Let's quickly setup both:
...
import { createStore, combineReducers } from 'redux'
import { reducer as formReducer } from 'redux-form'
...
const rootReducer = combineReducers({
form: formReducer,
// my other reducers come here
});
const store = createStore(rootReducer);
Now, we have a Redux store but to actually use this store with React components, we'll need to use Provider
component that comes with react-redux
package. It's quite simple (we just need to wrap our BrowserRouter
with Provider
and pass the store
as a prop) so, let's just look at our App.js
so far:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import {
BrowserRouter,
Link,
Route
} from 'react-router-dom';
import { createStore, combineReducers } from 'redux'
import { reducer as formReducer } from 'redux-form'
import { Provider} from 'react-redux'
// Other components are here collapsed
const rootReducer = combineReducers({
form: formReducer,
// my other reducers come here
});
const store = createStore(rootReducer);
class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<Provider store={store}>
<BrowserRouter>
<div>
<Header />
<Route exact path="/" component={LandingPage} />
<Route path="/signin" component={SignInPage} />
<Route path="/account/create" component={CreateAccountPage} />
<Route path="/dashboard" component={DashboardPage} />
</div>
</BrowserRouter>
</Provider>
);
}
};
ReactDOM.render(<App />, document.getElementById('app'));
Now, let's modify the SignInPage
component:
class SignInPage extends Component {
constructor(props) {
super(props);
this.processSubmit = this.processSubmit.bind(this);
}
componentWillMount() {
// do something like setting default state
}
processSubmit(values) {
// do something with the values
}
render() {
const { handleSubmit, submitting } = this.props;
return (
<div className="container mt-5">
<div className="row justify-content-center">
<div className="col-6">
<div className="card">
<div className="card-body">
<h2 className="text-center font-weight-light mb-4">Sign into your account</h2>
<form onSubmit={handleSubmit(this.processSubmit)}>
<Field
label="Email Address"
name="email"
component={FormField}
id="email"
type="text"
className="form-control"
/>
<Field label="Password" name="password" component={FormField} id="password" type="password" className="form-control" />
<div className="form-check">
<label className="form-check-label">
<Field name="remember" component="input" type="checkbox" className="form-check-input mt-2" value="1" />
Remember me
</label>
</div>
<div className="form-group mt-4">
<button type="submit" className="btn btn-secondary" disabled={submitting}>Continue</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
);
}
};
SignInPage = reduxForm({
form: 'signin',
validate
})(SignInPage);
There a few things here to digest before we move on:
-
SignInPage
component is no longer a pathetic dumb component that just renders the other components and passprops
. - The
reduxForm
decorator andField
component has been imported fromredux-form
package so, our import declaration forredux-form
now looks like this:
import {
reducer as formReducer,
reduxForm,
Field
} from 'redux-form'
- Notice the
Field
takes acomponent
as a prop. Any component that you specify will be rendered insideField
. The inner component that we specify gets some nice props that can be used to make the inputs interactive. Here's how I have written a basicFormField
component:
const FormField = ({
label,
input,
type,
name,
className,
meta: { touched, error, warning }
}) => (
<div className="form-group">
{
label &&
<label htmlFor={name}>{label}</label>
}
<input {...input } name={name} type={type} className={
`${className} ${
touched && (
(error && 'is-invalid')
)
}`
} />
{
touched &&
(error && <span className="invalid-feedback">{error}</span>)
}
</div>
);
- Look at this line carefully in the new
SignInPage
component:
const { handleSubmit, submitting } = this.props;
The handleSubmit
and submitting
props come from the decorated SignInPage component . handleSubmit
takes a callback with values
being passed as its first parameter. submitting
is just a boolean that tells if the form is in submitting state which is helpful in disabling the submit button to prevent multiple requests when the one is being processed.
- Also, notice
validate
property in the following lines:
SignInPage = reduxForm({
form: 'signin',
validate: validatorSignInForm,
})(SignInPage);
The validate
property must hold a function that returns error messages for fields. I'll be using a simple JavaScript based validation library validate.js
to implement validatorSignInForm
which you can install with npm install --save validate.js
. The validator function looks like below:
const validatorSignInForm = (values) => {
const result = validate(values, {
email: {
presence: {
message: '^Please enter your email address.'
},
email: {
message: '^Please enter a valid email address.'
}
},
password: {
presence: {
message: '^Please enter your password.'
}
}
});
return result;
};
You should know that validate()
comes from validate.js
library so, for now you just need to know that it takes values
as its first argument and rules for each value in nicely formed object as its second argument.
Now, that's a lot of code that we have looked over so far but, you'll see that once you have gone through and understood it, you'll encounter no problem creating interactive forms for your application.
Now, let's put the updated SignInPage
component along with the code we've written in App.js
so far and see how it looks like now:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import {
BrowserRouter,
Link,
Route
} from 'react-router-dom';
import { createStore, combineReducers } from 'redux'
import {
reducer as formReducer,
reduxForm,
Field
} from 'redux-form'
import { Provider} from 'react-redux'
// Other components are here collapsed
const validatorSignInForm = (values) => {
const result = validate(values, {
email: {
presence: {
message: '^Please enter your email address.'
},
email: {
message: '^Please enter a valid email address.'
}
},
password: {
presence: {
message: '^Please enter your password.'
}
}
});
return result;
};
class SignInPage extends Component {
constructor(props) {
super(props);
this.processSubmit = this.processSubmit.bind(this);
}
componentWillMount() {
// do something like setting default state
}
processSubmit(values) {
// do something with the values
}
render() {
const { handleSubmit, submitting } = this.props;
return (
<div className="container mt-5">
<div className="row justify-content-center">
<div className="col-6">
<div className="card">
<div className="card-body">
<h2 className="text-center font-weight-light mb-4">Sign into your account</h2>
<form onSubmit={handleSubmit(this.processSubmit)}>
<Field
label="Email Address"
name="email"
component={FormField}
id="email"
type="text"
className="form-control"
/>
<Field label="Password" name="password" component={FormField} id="password" type="password" className="form-control" />
<div className="form-check">
<label className="form-check-label">
<Field name="remember" component="input" type="checkbox" className="form-check-input mt-2" value="1" />
Remember me
</label>
</div>
<div className="form-group mt-4">
<button type="submit" className="btn btn-secondary" disabled={submitting}>Continue</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
);
}
};
SignInPage = reduxForm({
form: 'signin',
validate: validatorSignInForm
})(SignInPage);
const rootReducer = combineReducers({
form: formReducer,
// my other reducers come here
});
const store = createStore(rootReducer);
class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<Provider store={store}>
<BrowserRouter>
<div>
<Header />
<Route exact path="/" component={LandingPage} />
<Route path="/signin" component={SignInPage} />
<Route path="/account/create" component={CreateAccountPage} />
<Route path="/dashboard" component={DashboardPage} />
</div>
</BrowserRouter>
</Provider>
);
}
};
ReactDOM.render(<App />, document.getElementById('app'));
Break - Breath a little
So far, we have looked at a lot of code. You must be overwhelmed. Take a break. Inhale air. And be back where we start looking at processing the sign in form.
So, have you noticed that I had left processSubmit method of SignInPage
component empty? Our goal will now be to look at how we will implement it. A break-down of its implementation will be:
- Send an AJAX request to the login route with the values
- Wait for the response
- Do something upon the request results
Now, for sending AJAX requests, we will use axios
- a promise based AJAX library. Don't worry, it's already installed. You can start using it like below:
processSubmit(values) {
axios
.post('/ajax/login', values)
.then( (response) => {
console.log(response.data);
})
.error( (err) => {
console.log(err);
});
}
A few things to setup before you can see the request completing successfully:
- You need to put
Auth::routes();
inajax
Route group that we created in Part 1 in/routes/web.php
. - You need to make sure that you have
<meta>
tag in yourweb.blade.php
that contains the CSRF token otherwise, Laravel will throw TokenMismatchException instead of processing the login request. So make sure something like below is there:
<meta name="csrf-token" content="{{ csrf_token() }}">
- By default,
AuthenticatesUsers
trait inLoginController
has a methodsendLoginResponse
that is implemented to redirect. The problem is that we don't want to redirect instead, we want to return user details. So, copy the following lines of code and paste them as methods ofLoginController
which resides in/app/Http/Controllers/Auth/LoginController.php
:
/**
* Send the response after the user was authenticated.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendLoginResponse(Request $request)
{
$request->session()->regenerate();
$this->clearLoginAttempts($request);
$user = $this->guard()->user();
if($this->authenticated($request, $user)) {
return response()->json([
'success' => true,
'user' => $user
], 200);
}
}
/**
* The user has been authenticated.
*
* @param \Illuminate\Http\Request $request
* @param mixed $user
* @return mixed
*/
protected function authenticated(Request $request, $user)
{
return true;
}
- Similarly, the
AuthenticatesUsers
trait hassendFailedLoginResponse
which also needs to be overridden. So, copy the below lines intoLoginController
:
/**
* Get the failed login response instance.
*
* @param \Illuminate\Http\Request $request
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function sendFailedLoginResponse(Request $request)
{
return response()->json([
'success' => false,
'message' => trans('auth.failed')
], 422);
}
Now, if you test by submitting the form, you'll be able to receive the JSON responses. Or it doesn't? Let me know.
To be continued in Part 3. In Part 3, will continue with processSubmit
for the SignInPage
component. And, we will also look at how we can manage the user details using redux, prevent routes from being accessed in guest mode and much more.
The reason I have to split this into part 3 is because the dev.to post editor has become slow due to large amount of content in it and it's annoying!
Top comments (9)
Hi
Thanks for this tutorial. I'm getting "View [auth.login] not found." on the login option, and similar for the Register option.
Any idea why that might be?
Thanks
In your routes file, change this:
Route::get('/{path?}', function () {
return view('web');
});
to this:
Route::get('/{path?}', function () {
return view('web')->with('user', Auth::user());
})->where('path', '.*');
Ok. Tried that, didn't seem to make any difference
Can you show exactly how your routes file look like? You might want to place Auth::routes(); in the /ajax block.
routes/web.php -
<?php
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::group(['prefix' => 'ajax'], function() {
// all routes that don't need to go to react-router
Auth::routes();
});
Route::get('/{path?}', function () {
return view('web')->with('user', Auth::user());
})->where('path', '.*');
Please post the part 3.. Your post is very helpful.. Thanks
where part 3? T.T
there is just problem, with axios, there is no error method on it, instead we need to use catch method. Other than that, awesome content.
Awesome content, when will the next part will be published? Thanks for sharing.