Sütik

Sütiket használunk a tartalom személyre szabására és a forgalom elemzésére. Kérjük, határozza meg, hogy hajlandó-e elfogadni weboldalunkon a sütiket.

Oldal tetejére
Bezárás
Zengo - NKFIA pályázat Zengo - Széchenyi2020
Zengo - Hogyan hozzunk létre React webalkalmazást a Laravel Sanctum segítségével - 1. rész: BEJELENTKEZÉS
Kategória:

Hogyan hozzunk létre React webalkalmazást a Laravel Sanctum segítségével - 1. rész: BEJELENTKEZÉS

Zengo - óra3 perc olvasási idő
2022. 04. 28.

A Laravel az elmúlt években a legtöbbször alkalmazott megoldássá vált a PHP-ben írt webalkalmazások körében. Ez nem is meglepő, hiszen rengeteg kész megoldással rendelkezik. A Laravel egyik legnagyobb előnye az Authentication Starter Kit, ha alapértelmezett blade sablont vagy a Vue-t szeretnénk használni. De mit tegyünk, ha atomok csorognak az ereinkben, és a szívünkbe lopta magát a React?

Bízunk benne, hogy sikerül választ adnunk a kérdésre, ugyanis ebben a cikkben egy Laravelbe csomagolt ReactJS webalkalmazást hozunk létre, amely később a REST API háttérrendszerünkké válhat. Kezdjük hozzá!

Miután elindítottuk új projektünket, telepítsük azt a csomagot, amely előre beállítja a React frontendet.

composer require laravel/ui

Ezután futtathatjuk ezt a parancsot.

php artisan ui react

Ezek után új fájlok kerülnek a projektünkbe, így telepítenünk kell az npm csomagjait.

npm install

Láthatjuk, hogy a webpack.mix.js fájlunk megváltozott, így buildelhetjük a React webalkalmazásunkat.

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

Mostantól az resources > js könyvtárnak így kell kinéznie:

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

Belépési pont

Mindenekelőtt egy nézetet kell készítenünk, amelyet a Laravel routing rendszere betölt és ahol a JavaScript kódunk lefut. Hozzunk létre egy új view-t app.blade.php néven.

>>> 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>

Hozzunk létre egy új fájlt App.js néven a resources/js/components könyvtárba. Ez lesz a fő komponensünk.

>>> 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'));
}

Ekkor az app.js fájlba belefoglaljuk az új komponensünket.

>>> /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');

Amint azt korábban említettem, a Laravel Routing a blade nézeten keresztül tölti be a webalkalmazásunkat. A web.php-ben a következőképpen módosíthatjuk a routing-ot ehhez:

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

Most már végre futtathatjuk webalkalmazásunkat!

npm run watch

Amikor megnyitjuk a böngészőt, valami ilyesmit kell látnunk: 100%

Laravel Sanctum

A Laravel Sanctum egy alapvető API token alapú hitelesítési őr. Ez a legjobb választás SPA és mobil alkalmazásokhoz. Először is telepítsük!

composer require laravel/sanctum

Tegyük közzé a Sanctum konfigurációs és migrációs fájljait.

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

Migrate.

php artisan migrate

Mivel a Sanctumot szeretnénk beléptetésre használni, a Sanctum middlewaret hozzá kell adnunk az api middleware csoporthoz az app/Http/Kernel.php fájlban.

'api' => [
     \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
    'throttle:api',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],
A bejelentkezés implementálása

Mivel a hitelesítési módszerünket az alapoktól építjük fel, ezért érdemes létrehoznunk saját LoginControllerünket.

php artisan make:controller LoginController

A következő kód segítségével a bejelentkezni próbáló felhasználót hitelesíthetjük.

<?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);
    }
}

Most már elkészíthetjük az új bejelentkezési útvonalat.

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

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

Bejelentkezési oldal / Material UI

A Material UI egy nagyszerű UI könyvtár. Használatával könnyedén létrehozhatunk tetszetős oldalakat. Állítsuk le a npm run watch-ot és telepítsük a következő npm csomagokat.

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

Hozzunk létre egy új komponenst, ez lesz a bejelentkezési oldalunk:

>>> 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

Az App.js-ben használhatjuk új bejelentkezési oldalunkat.

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'));
}

Egy frissítés után az alkalmazásunknak így kellene kinéznie: 100%

Felhasználó létrehozása

A bejelentkezéshez természetesen szükségünk van néhány felhasználóra. Most nem akartunk felhasználó-létrehozó oldalt, metódusokat, útvonalakat és társait megvalósítani, egyszerűen csak a "PHP artisan’s tinker" parancsot használjuk. Ez egy nagyszerű eszköz a PHP kód futtatásához a terminálban.

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

Így létre is hoztuk az első felhasználónkat!

Belépési adatok elküldése

El kell küldenünk a hitelesítő adatainkat a háttérrendszerhez. Ha a bejelentkezési kísérlet sikeres volt, akkor a Laravel a session-cookie-k segítségével hitelesít bennünket.

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!')
    })
}

Felhasználói példány

Mivel nem szeretnénk minden betöltés alkalmával alapinformációkat lekérni a háttérrendszerünktől, ezért tárolhatunk néhány változót a böngésző helyi tárhelyén. A könnyebb hozzáférés érdekében létrehozunk egy User osztályt, amely betölti az adatokat a tárhelyről. A sikeres hitelesítés után ez az osztály lesz a felelős a válaszban megkapott adatok kezeléséért, és rendelkezik egy alapvető metódussal is, amellyel eldöntheti, hogy a felhasználó hitelesített-e.

Hozzunk létre egy új fájlt (és könyvtárat): 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

Amikor hitelesítettük a felhasználót, átirányítjuk egy privát helyre. Hozzunk létre egy komponenst, amelyet csak a bejelentkezett felhasználók láthatnak.

>>> 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

A React Router könyvtárat fogjuk használni a kliens oldali navigációhoz.

npm install react-router-dom

Védett oldal

Alapértelmezés szerint a React Router csomag nem kezeli a bejelentkezéshez kötött útvonalakat. Ezzel a logikával kellene kiegészítenünk.

Létrehozunk egy új komponenst ProtectedRoute néven. Ez egy Route komponenssel fog visszatérni. A hitelesítés a render() metódusban megy végig. Ellenőrizzük, hogy a felhasználó be van-e jelentkezve: ha teljesíti az eredeti komponenst (amelyet paraméterként adunk át) renderelésre kerül, ha nem, akkor átirányítjuk a bejelentkezési oldalra.

>>> 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 }
                       }}/>
                   }
               }
        />
    )
}

Most meghatározzuk útvonalainkat: az egyszerűt és a védettet.

>>> 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>
    );
}

Láthatjuk, hogy a ProtectedRoute ugyanúgy működik, mint a sima Route. Pontosan ugyanazokat a paramétereket tudjuk átadni.

A Login komponensben a User osztály hitelesítési metódusát hívjuk meg. Sikeres bejelentkezés után a visszatérés átirányítja a felhasználót a védett útvonalra. A withRouter() segítségével eljuttatjuk az előzményeket és a hely-információkat a Login komponensnek.

>>> 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)

Van egy hiányzó pont a routing rendszerünkben. Mostantól a Laravel csak akkor irányít át a React webalkalmazásra, ha a kérés az domain.com/app útvonalra érkezik. Hozzá kell adnunk még egy paramétert az olyan útvonalak kezeléséhez, mint az app/login, app/dashboards stb.

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

Ezután a böngészőben lépjünk az /auth/login oldalra és próbáljuk ki a korábban létrehozott felhasználót.

Ezzel be is jelentkeztünk! 100%

A működő megoldást GitHub-fiókban is közzétettük. A következő fejezetben bemutatjuk, hogyan lehet helyesen kijelentkezni a frissen létrehozott webes alkalmazásunkból! Hasonló szakmai írásokért olvasd el további blogbejegyzéseinket!


A cikk eredeti változata elérhető itt. Fordította: Kádár Kyra