Cookies

We use cookies to improve your browsing experience on our site, show personalized content and analyze site traffic. By choosing "I Accept", you consent to our use of cookies and other tracking technologies.

To the top
Close
Zengo - Build a React Web App with Laravel Sanctum - Part 1: LOGIN
Category:

Build a React Web App with Laravel Sanctum - Part 1: LOGIN

Zengo - óra2 minutes reading time
2022. 04. 28.

Laravel in the past few years became an industry leading solution, if we speak about Web Application written in PHP. No wonder why, it has tons of out of the box solutions. On of the greatest advantage of Laravel is the Authentication Starter Kit, if you want to use the default blade template or Vue. But what should we do if atoms trickle in your veins , and your heart have been stolen by React?

Well, I hope we could find answers for our questions, because in this article we will create a ReactJS Web Application wrapped into Laravel, which is later could became our REST API backend.

After we boot up our new project let’s install the package, which will preset our React frontend:

composer require laravel/ui

Then we can run this command:

php artisan ui react

New files will be added to our project, we should install the npm’s packages:

npm install

As you can see, our webpack.mix.js had been changed, so we could built our React Web App:

mix.js('resources/js/app.js', 'public/js')
    .react()
    .sass('resources/sass/app.scss', 'public/css');

From now your resources > js directory should looks like this:

- resources
- - js
- - - components
- - - - Exmaple.js
- - - app.js
- - - bootstrap.js

Entry point

First of all, we should make a view, what Laravel routing will load and where our JavaScript code will be executed. Create a new view called app.blade.php.

>>> resources/views/app.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="initial-scale=1, width=device-width"/>
        <title>Laravel & React</title>

    </head>

    <body>
        <div id="app"></div>       
        <script src="{{asset('js/app.js')}}"></script>
    </body>

</html>

Create a new file as App.js into resources/js/components directory. This will be our main component.

>>> resources/js/components/App.js 

import React from 'react';
import ReactDOM from 'react-dom';

function App() {
    return (
        <div>
            Hello
        </div>
    );
}

export default App;

if (document.getElementById('app')) {
    ReactDOM.render(<App />, document.getElementById('app'));
}

In the app.js file we include our new component.

>>> /resources/js/app.js
/**
 * First we will load all of this project's JavaScript dependencies which
 * includes React and other helpers. It's a great starting point while
 * building robust, powerful web applications using React + Laravel.
 */

require('./bootstrap');

/**
 * Next, we will create a fresh React component instance and attach it to
 * the page. Then, you may begin adding components to this application
 * or customize the JavaScript scaffolding to fit your unique needs.
 */

require('./components/App');

As I mentioned previosuly, Laravel Routing will load our Web App trought the blade view. Here is how we change routing in web.php to do that:

Route::get('/', function () {
    return view('app');
});

Finally we could build our web app for the first time.

npm run watch

When you open your browser you should see something like that: 100%

Laravel Sanctum

Laravel Sanctum is a basic API token based authentication guard. This is the best choice for SPA and mobil application. Let’s install it!

composer require laravel/sanctum

Published the Sanctum’s configuration and migration files.

php artisan vendor:publish --
provider="Laravel\Sanctum\SanctumServiceProvider"

Migrate.

php artisan migrate

Because want to use Sanctum for authentication, we should add Sanctum’s middleware in our api middleware within our app/Http/Kernel.php.

'api' => [
     \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
    'throttle:api',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],
Login implementation

We are building our authentication method from the skretch, that’s why we should create our own LoginController.

php artisan make:controller LoginController

We implement the login attempt in authentication method.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    public function authenticate(Request $request)
    {
        $credentials = $request->validate([
            'email' => ['required', 'email'],
            'password' => ['required'],
        ]);

        if (Auth::attempt($credentials)) {
            $request->session()->regenerate();
            $user = Auth::user();

            return response()->json($user);
        }

        return response()->json([
            'errors' => [
                'email' => 'The provided credentials do not match our records.',
                ]
        ], 422);
    }
}

Now we can define the new login route.

>>> routes/api.php 
use App\Http\Controllers\LoginController;

Route::post('login', [LoginController::class, 'authenticate']);

Login page / Material UI

Material UI is a great UI library. Using this we can make pretty things quite easily. Stop then npm run watch and install the following npm packages:

npm install @mui/material @emotion/react @emotion/styled

Create a new component, this will be our login page:

>>> resources/js/components/Login
import React from "react";
import {
    Box, 
    Button,
    Container,
    TextField,
    CssBaseline,
    Typography
} from "@mui/material";

function Login() {
    const handleSubmit = (event) => {
        //TODO handle form submit
    }

    return (
        <Container maxWidth={"xs"}>
            <CssBaseline/>
            <Box
                sx={{
                    marginTop: 8,
                    display: 'flex',
                    flexDirection: 'column',
                    alignItems: 'center',
                }}>
                <Typography component={"h1"} variant={"h5"}>
                    Login
                </Typography>
                <Box component={"form"} onSubmit={handleSubmit}>
                    <TextField
                        margin="normal"
                        required
                        fullWidth
                        id="email"
                        label="E-mail"
                        name="email"
                        autoComplete="email"
                        autoFocus
                    />
                    <TextField
                        margin="normal"
                        required
                        fullWidth
                        name="password"
                        label="Password"
                        type="password"
                        id="password"
                        autoComplete="current-password"
                    />
                    <Button
                        fullWidth
                        variant={"outlined"}
                        type={"submit"}
                        sx={{ mt: 3, mb: 2 }}
                    >
                        Login
                    </Button>
                </Box>
            </Box>
        </Container>
    )
}

export default Login

In App.js we can use our new Login page:

import React from 'react';
import ReactDOM from 'react-dom';
import Login from "./Login/Login";

function App() {
    return (
        <React.Fragment>
            <Login/>
        </React.Fragment>
    );
}

export default App;

if (document.getElementById('app')) {
    ReactDOM.render(<App />, document.getElementById('app'));
}

After a fresh built, our app should looks like this: 100%

Create user

Of course, we should’ve some user for login. Now we didn’t want to implement a user creation page, methods, routes etc… We simply use the artisan’s tinker comand. This is a great tool for running php coding in cli.

php artisan tinker
>>> App\Models\User::create([‘name’=> ‘Test User’, ‘email’=> ‘test@test.com’, ‘password’ => bcrypt(‘test’)]);

That’s all, we create our first user!

Handle the submit

We will send our credentials for the backend. If the sign in attempt was successfull, then Laravel will authenticate us using the session cookies.

const handleSubmit = (event) => {
    event.preventDefault()
    const formData = new FormData(event.currentTarget);

    const loginCredentials = {
        email: formData.get('email'),
        password: formData.get('password')
    }

    window.axios.post('/api/login', loginCredentials).then((response) => {
        console.log('Logged successfully!')
    })
}

User Instance

We don’t want to ask our backend for basic information every time we load the page. We could store some variable in the browser’s local storage. For easier access we create a User class, when it’s implemented it will load the data from the storage. After a successful authentication this class will be responsible handle the response’s data, and also has basic method to decied if the user is authenticated.

Create a new file (and directory): resources/js/auth/user.js

class User {

    constructor() {
        this.init()
    }

    init() {
        this.name = localStorage.getItem('userName')
        this.email = localStorage.getItem('userEmail')
        this.loggedIn = localStorage.getItem('userLoggedIn')
    }

    /**
     *
     * @param data object
     * @param data.name string
     * @param data.email string
     * @param callback function
     */
    authenticated(data, callback) {
        localStorage.setItem('userName', data.name)
        localStorage.setItem('userEmail', data.email)
        localStorage.setItem('userLoggedIn', true)

        this.init()

        callback()
    }

    /**
     *
     * @return {boolean}
     */
    isLoggedIn() {
        return Boolean(this.loggedIn) === true
    }
}

export default new User()

Routing

Now we have authenticated user, redirect to somewhere private. Create a component, which one only the logged users will see.

>>> resources/js/components/Dashboard/Dashboard.js
import React from "react";
import user from "../../Models/user";
import {Container, Grid, Paper, Typography} from "@mui/material";

function Dashboard() {
    return (
        <React.Fragment>
            <Container>
                <Grid container justifyContent={"center"}>
                    <Grid item md={12}>
                        <Typography variant={"h5"}>
                            Hello {user.name}, you're logged in!
                        </Typography>
                    </Grid>
                </Grid>
            </Container>
        </React.Fragment>
    )
}

export default Dashboard

React Router

We are going to use the React Router library for client-side routing.

npm install react-router-dom

Protected Route

In default, React Router package doesn’t support authenticated routes handling. We should implement this logic.

We create a new component called ProtectedRoute. This return with a Route component. The authentication is in the render method. We check if the user is logged in, if it’s fullfiled the original component (which is passed by as prop) will be render, if not we redirect to login page.

>>> resources/js/components/ProtectedRoute/ProtectedRoute.js
import React from "react";
import {Redirect, Route} from "react-router-dom";
import user from "../../Models/user";

export const ProtectedRoute = ({component: Component, ...rest}) => {
    return (
        <Route {...rest}
               render={
                   (props) => {
                       if (user.isLoggedIn()) {
                           return <Component {...props} {...rest}/>
                       }
                       return <Redirect to={{
                           pathname: "/app/login",
                           state: { from: props.location }
                       }}/>
                   }
               }
        />
    )
}

Now we define our routes: the simple and the protected.

>>> resources/js/components/App.js
function App() {
    return (
        <React.Fragment>
            <BrowserRouter>
                <Route path={'/app/login'}>
                    <Login/>
                </Route>
                <ProtectedRoute
                    exact path={'/app/dashboard'}
                    component={Dashboard}
                />
            </BrowserRouter>
        </React.Fragment>
    );
}

As you can see, our ProtectedRoute works as the same as Route. We can pass exactly the same props.

In the Login component we call the authentication method of the User class. After a successful login the callback function will redirect the user to the protected route. We use the withRouter() to pass the history and location props to the Login componenets.

>>> resources/js/componenets/Login/Login.js
import React from "react";
import {
    Box,
    Button,
    Container,
    TextField,
    CssBaseline,
    Typography
} from "@mui/material";
import user from "../../Models/user";
import {withRouter} from "react-router-dom";

function Login({history, location}) {

    const handleSubmit = (event) => {
        event.preventDefault()
        const formData = new FormData(event.currentTarget);

        const loginCredentials = {
            email: formData.get('email'),
            password: formData.get('password')
        }

        function authenticatedCallback() {
            let {from} = location.state || {from: {pathname: '/'}}
            history.replace(from)
        }

        window.axios.post('/api/login', loginCredentials).then((response) => {
            user.authenticated(response.data, authenticatedCallback)
        })
    }

    return (
        <Container maxWidth={"xs"}>
            <CssBaseline/>
            <Box
                sx={{
                    marginTop: 8,
                    display: 'flex',
                    flexDirection: 'column',
                    alignItems: 'center',
                }}>
                <Typography component={"h1"} variant={"h5"}>
                    Login
                </Typography>
                <Box component={"form"} onSubmit={handleSubmit}>
                    <TextField
                        margin="normal"
                        required
                        fullWidth
                        id="email"
                        label="E-mail"
                        name="email"
                        autoComplete="email"
                        autoFocus
                    />
                    <TextField
                        margin="normal"
                        required
                        fullWidth
                        name="password"
                        label="Password"
                        type="password"
                        id="password"
                        autoComplete="current-password"
                    />
                    <Button
                        fullWidth
                        variant={"outlined"}
                        type={"submit"}
                        sx={{mt: 3, mb: 2}}
                    >
                        Login
                    </Button>
                </Box>
            </Box>
        </Container>
    )
}

export default withRouter(Login)

We have one missing point in our routing system. Now Laravel only redirects to React web app when the url pattern is matched to domain.com/app. We should add one more param to handle routes like app/login, app/dashboards, etc.

>>> routes/web.php
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
    return redirect('/app/dashboard');
});
Route::get('/app/{path?}', function () {
    return view('app');
});

On your browser got to the /auth/login page and try to log in with the user, which we created previously.

Finally we are logged in! 100%

We also published the working solution to GitHub. In the next chapter we cover how to sign out correctly from our freshly created web application! For more similar professional writings, read our other blog posts!


The original version of the article is available here.