Hướng dẫn react php authentication - phản ứng xác thực php

Đây là bài đầu tiên trong series: Hôm nay ăn gì với Laravel, ReactJS, React Hook, Redux Saga. Nếu các bạn chưa rõ mục đích mình tạo series này thì vui lòng ấn vào link và đọc nha.

Thật ra ban đầu mình không định làm chức năng này vì chỉ muốn cho một người sử dụng web thôi. Nhưng rồi mình nghĩ có lẽ nên phân chia quản lý ra nhiều users thì sẽ thiết thực hơn. Hãy cùng đi từ phía backend trước nhé

Backend

Hiện tại laravel đã ra tới bản 7.0, và câu lệnh

<script type="text/javascript">window.GLOBALS={"user":{!! json_encode($user) !!}}</script>
<script src="{{ asset('js/app.js') }}"></script>
0 đã không còn sử dụng được nữa. Nhưng đương nhiên không phải là Laravel đã khai tử nó, đơn giản chỉ là chuyển sang 1 cách khác thôi. Đầu tiên bạn hãy chạy command
<script type="text/javascript">window.GLOBALS={"user":{!! json_encode($user) !!}}</script>
<script src="{{ asset('js/app.js') }}"></script>
1. Sau đó thì sử dụng
<script type="text/javascript">window.GLOBALS={"user":{!! json_encode($user) !!}}</script>
<script src="{{ asset('js/app.js') }}"></script>
2, nếu bạn dùng vue thì có thể thay react = vue. Tiếp tới thì các bạn chỉnh sửa file migration sao cho phù hợp rồi chạy migrate là được. Giờ thì bạn có thể sử dụng các chức năng như đăng nhập hay tạo tài khoản, nhưng ở đây mình sử dụng reactjs nên mình cần custom lại chút thì mới có thể sử dụng được. Nếu bạn chưa cách kết hợp reactjs trong laravel thì có thể đọc bài viết Sử dụng ReactJs trong project Laravel của mình nhé.

Giờ mình sẽ tạo

<script type="text/javascript">window.GLOBALS={"user":{!! json_encode($user) !!}}</script>
<script src="{{ asset('js/app.js') }}"></script>
3 để xử lý việc đăng nhập và đăng ký. Đăng ký thì sẽ khá đơn giản, chỉ là chúng ta tạo ra một người dùng mới trên hệ thống, nếu các bạn có thời gian thì hãy làm cả chức năng xác nhận email nhé. Lưu ý vì chúng ta sử dụng react nên respone trả về phải là json nhé.

    public function register(RegisterRequest $request)
    {
        $data = $request->all();
        $data['password'] = bcrypt($request->password);
        $user = User::create($data);

        return response()->json([
            'user' => $user,
            'message' => 'Đăng ký thành công',
        ], 200);

    }

Còn đối với đăng nhập, ban đầu mình có sử dụng

<script type="text/javascript">window.GLOBALS={"user":{!! json_encode($user) !!}}</script>
<script src="{{ asset('js/app.js') }}"></script>
4 thì không được, và mình đã vào hẳn trong function của laravel và xem

use App\Http\Requests\Auth\LoginRequest;
use App\Http\Requests\Auth\RegisterRequest;
use App\User;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    use AuthenticatesUsers;
    
    public function login(LoginRequest $request)
    {
        if ($this->attemptLogin($request)) {
            $user = Auth::user();

            return $this->sendLoginResponse($request, $user);
        }

        return $this->sendFailedLoginResponse($request);
    }

    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

    protected function sendLoginResponse(Request $request, $user)
    {
        $request->session()->regenerate();

        $this->clearLoginAttempts($request);

        if ($response = $this->authenticated($request, $this->guard()->user())) {
            return $response;
        }

        return response()->json([
            'data' => $user,
            'message' => 'Đăng nhập thành công',
        ], 200);
    }

    protected function sendFailedLoginResponse()
    {
        return response()->json([
            'message' => 'Email hoặc mật khẩu của bạn không chính xác',
        ], 400);
    }
}

Ở trong file

<script type="text/javascript">window.GLOBALS={"user":{!! json_encode($user) !!}}</script>
<script src="{{ asset('js/app.js') }}"></script>
5 bạn nhớ thêm đoạn này nhé

<script type="text/javascript">window.GLOBALS={"user":{!! json_encode($user) !!}}</script>
<script src="{{ asset('js/app.js') }}"></script>

Frontend

Phân chia thư mục

Vì phải sử dụng thêm

<script type="text/javascript">window.GLOBALS={"user":{!! json_encode($user) !!}}</script>
<script src="{{ asset('js/app.js') }}"></script>
6 nên bước này ban đầu sẽ mất khá lâu để setup. Mình sẽ nói qua về cách mình chia thư mục đã nhé:

Hướng dẫn react php authentication - phản ứng xác thực php

Tất cả source code sẽ được lưu trong

<script type="text/javascript">window.GLOBALS={"user":{!! json_encode($user) !!}}</script>
<script src="{{ asset('js/app.js') }}"></script>
7. Mình có chia nhỏ ra thêm thành các thư mục con như sau:

components

Đây sẽ là nơi chứa các

<script type="text/javascript">window.GLOBALS={"user":{!! json_encode($user) !!}}</script>
<script src="{{ asset('js/app.js') }}"></script>
8 của mình. Mình có phân ra thành 2 loại nhỏ nữa là
<script type="text/javascript">window.GLOBALS={"user":{!! json_encode($user) !!}}</script>
<script src="{{ asset('js/app.js') }}"></script>
9 và
import 'babel-polyfill'; // Needed for redux-saga es6 generator support
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware, { END } from 'redux-saga';
import { fromJS } from 'immutable';

const sagaMiddleware = createSagaMiddleware();

export default function configStore(initialState = {}, history, rootReducer) {
    const middlewares = [
        sagaMiddleware
    ];

    const enhancers = [
        applyMiddleware(...middlewares)
    ];

    /* eslint-disable no-underscore-dangle */
    const composeEnhancers =
        process.env.APP_ENV !== 'production' &&
        typeof window === 'object' &&
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
            window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
    /* eslint-enable */

    const store = createStore(
        rootReducer(),
        fromJS(initialState),
        composeEnhancers(...enhancers)
    );

    store.runSaga = sagaMiddleware.run;
    store.close = () => store.dispatch(END);

    return store;
}
0.
<script type="text/javascript">window.GLOBALS={"user":{!! json_encode($user) !!}}</script>
<script src="{{ asset('js/app.js') }}"></script>
9 chính là nơi lưu trữ các view mà các bạn có thể truy cập. Còn
import 'babel-polyfill'; // Needed for redux-saga es6 generator support
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware, { END } from 'redux-saga';
import { fromJS } from 'immutable';

const sagaMiddleware = createSagaMiddleware();

export default function configStore(initialState = {}, history, rootReducer) {
    const middlewares = [
        sagaMiddleware
    ];

    const enhancers = [
        applyMiddleware(...middlewares)
    ];

    /* eslint-disable no-underscore-dangle */
    const composeEnhancers =
        process.env.APP_ENV !== 'production' &&
        typeof window === 'object' &&
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
            window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
    /* eslint-enable */

    const store = createStore(
        rootReducer(),
        fromJS(initialState),
        composeEnhancers(...enhancers)
    );

    store.runSaga = sagaMiddleware.run;
    store.close = () => store.dispatch(END);

    return store;
}
0 sẽ là nơi chưa
import 'babel-polyfill'; // Needed for redux-saga es6 generator support
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware, { END } from 'redux-saga';
import { fromJS } from 'immutable';

const sagaMiddleware = createSagaMiddleware();

export default function configStore(initialState = {}, history, rootReducer) {
    const middlewares = [
        sagaMiddleware
    ];

    const enhancers = [
        applyMiddleware(...middlewares)
    ];

    /* eslint-disable no-underscore-dangle */
    const composeEnhancers =
        process.env.APP_ENV !== 'production' &&
        typeof window === 'object' &&
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
            window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
    /* eslint-enable */

    const store = createStore(
        rootReducer(),
        fromJS(initialState),
        composeEnhancers(...enhancers)
    );

    store.runSaga = sagaMiddleware.run;
    store.close = () => store.dispatch(END);

    return store;
}
3 (Route sử dụng cho nhưng user không đăng nhập, ở đây hiện tại sẽ chỉ có view đăng nhập và đăg ký) và
import 'babel-polyfill'; // Needed for redux-saga es6 generator support
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware, { END } from 'redux-saga';
import { fromJS } from 'immutable';

const sagaMiddleware = createSagaMiddleware();

export default function configStore(initialState = {}, history, rootReducer) {
    const middlewares = [
        sagaMiddleware
    ];

    const enhancers = [
        applyMiddleware(...middlewares)
    ];

    /* eslint-disable no-underscore-dangle */
    const composeEnhancers =
        process.env.APP_ENV !== 'production' &&
        typeof window === 'object' &&
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
            window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
    /* eslint-enable */

    const store = createStore(
        rootReducer(),
        fromJS(initialState),
        composeEnhancers(...enhancers)
    );

    store.runSaga = sagaMiddleware.run;
    store.close = () => store.dispatch(END);

    return store;
}
4 (Route sử dụng cho các user đã đăng nhập)

shared

Đây sẽ là nơi mình lưu nhưng config để dùng

stores

Nếu bạn sử dụng

<script type="text/javascript">window.GLOBALS={"user":{!! json_encode($user) !!}}</script>
<script src="{{ asset('js/app.js') }}"></script>
6 thì đã biết răng nó sẽ tạo ra 1 stores chung để lấy dữ liệu, và đây chính là nơi để mình code những chức năng liên quan

utils

Đây là nhưng function mà mình sẽ sử dụng lại ở nhiều nơi

Redux saga

Để sự dụng được

import 'babel-polyfill'; // Needed for redux-saga es6 generator support
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware, { END } from 'redux-saga';
import { fromJS } from 'immutable';

const sagaMiddleware = createSagaMiddleware();

export default function configStore(initialState = {}, history, rootReducer) {
    const middlewares = [
        sagaMiddleware
    ];

    const enhancers = [
        applyMiddleware(...middlewares)
    ];

    /* eslint-disable no-underscore-dangle */
    const composeEnhancers =
        process.env.APP_ENV !== 'production' &&
        typeof window === 'object' &&
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
            window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
    /* eslint-enable */

    const store = createStore(
        rootReducer(),
        fromJS(initialState),
        composeEnhancers(...enhancers)
    );

    store.runSaga = sagaMiddleware.run;
    store.close = () => store.dispatch(END);

    return store;
}
6 thì chúng ta sẽ phải thay đổi 1 chút ở file
import 'babel-polyfill'; // Needed for redux-saga es6 generator support
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware, { END } from 'redux-saga';
import { fromJS } from 'immutable';

const sagaMiddleware = createSagaMiddleware();

export default function configStore(initialState = {}, history, rootReducer) {
    const middlewares = [
        sagaMiddleware
    ];

    const enhancers = [
        applyMiddleware(...middlewares)
    ];

    /* eslint-disable no-underscore-dangle */
    const composeEnhancers =
        process.env.APP_ENV !== 'production' &&
        typeof window === 'object' &&
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
            window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
    /* eslint-enable */

    const store = createStore(
        rootReducer(),
        fromJS(initialState),
        composeEnhancers(...enhancers)
    );

    store.runSaga = sagaMiddleware.run;
    store.close = () => store.dispatch(END);

    return store;
}
7. Trước tiên cần tạo 1 file
import 'babel-polyfill'; // Needed for redux-saga es6 generator support
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware, { END } from 'redux-saga';
import { fromJS } from 'immutable';

const sagaMiddleware = createSagaMiddleware();

export default function configStore(initialState = {}, history, rootReducer) {
    const middlewares = [
        sagaMiddleware
    ];

    const enhancers = [
        applyMiddleware(...middlewares)
    ];

    /* eslint-disable no-underscore-dangle */
    const composeEnhancers =
        process.env.APP_ENV !== 'production' &&
        typeof window === 'object' &&
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
            window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
    /* eslint-enable */

    const store = createStore(
        rootReducer(),
        fromJS(initialState),
        composeEnhancers(...enhancers)
    );

    store.runSaga = sagaMiddleware.run;
    store.close = () => store.dispatch(END);

    return store;
}
8 trong
import 'babel-polyfill'; // Needed for redux-saga es6 generator support
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware, { END } from 'redux-saga';
import { fromJS } from 'immutable';

const sagaMiddleware = createSagaMiddleware();

export default function configStore(initialState = {}, history, rootReducer) {
    const middlewares = [
        sagaMiddleware
    ];

    const enhancers = [
        applyMiddleware(...middlewares)
    ];

    /* eslint-disable no-underscore-dangle */
    const composeEnhancers =
        process.env.APP_ENV !== 'production' &&
        typeof window === 'object' &&
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
            window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
    /* eslint-enable */

    const store = createStore(
        rootReducer(),
        fromJS(initialState),
        composeEnhancers(...enhancers)
    );

    store.runSaga = sagaMiddleware.run;
    store.close = () => store.dispatch(END);

    return store;
}
9

import 'babel-polyfill'; // Needed for redux-saga es6 generator support
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware, { END } from 'redux-saga';
import { fromJS } from 'immutable';

const sagaMiddleware = createSagaMiddleware();

export default function configStore(initialState = {}, history, rootReducer) {
    const middlewares = [
        sagaMiddleware
    ];

    const enhancers = [
        applyMiddleware(...middlewares)
    ];

    /* eslint-disable no-underscore-dangle */
    const composeEnhancers =
        process.env.APP_ENV !== 'production' &&
        typeof window === 'object' &&
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
            window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
    /* eslint-enable */

    const store = createStore(
        rootReducer(),
        fromJS(initialState),
        composeEnhancers(...enhancers)
    );

    store.runSaga = sagaMiddleware.run;
    store.close = () => store.dispatch(END);

    return store;
}

Ở phần import là những packet mà mình sử dụng, các bạn nhớ cài đặt nhé. Ở đây mình có sử dụng thư viện

import { combineReducers } from 'redux-immutable';

export default function rootReducer(asyncReducers) {
}
0. Nếu bạn chưa biết về khái niệm này thì có thể đọc bài này nha: Immutability và Immutable.js trong ReactJs. Xuyên suốt dự án mình sẽ sử dụng nó đó.

Giờ hãy cùng thiết lập store nhé, trong thư mục

import { combineReducers } from 'redux-immutable';

export default function rootReducer(asyncReducers) {
}
1 sẽ bao gồm những files và folder sau:
Hướng dẫn react php authentication - phản ứng xác thực php

Trong folder

import { combineReducers } from 'redux-immutable';

export default function rootReducer(asyncReducers) {
}
2 sẽ là những file tương ứng với từng view. Ví dụ mình làm cho chức năng đăng nhập và đăng ký thì sẽ tạo file
import { combineReducers } from 'redux-immutable';

export default function rootReducer(asyncReducers) {
}
3.
import { combineReducers } from 'redux-immutable';

export default function rootReducer(asyncReducers) {
}
4 là nơi để mình sẽ
import { combineReducers } from 'redux-immutable';

export default function rootReducer(asyncReducers) {
}
5 tất cả những
import { combineReducers } from 'redux-immutable';

export default function rootReducer(asyncReducers) {
}
6 lại. Trong
import { combineReducers } from 'redux-immutable';

export default function rootReducer(asyncReducers) {
}
7 thì mình sẽ
import { combineReducers } from 'redux-immutable';

export default function rootReducer(asyncReducers) {
}
8 tất cả các saga. Nói thì khá khó hình dung với những bạn mới, chút nữa mình sẽ đưa ra code cụ thể có lẽ các bạn sẽ dễ hình dung hơn. Hiện tại chưa có gì nên các file sẽ chỉ như sau:

import { combineReducers } from 'redux-immutable';

export default function rootReducer(asyncReducers) {
}
9:

import { combineReducers } from 'redux-immutable';

export default function rootReducer(asyncReducers) {
}

import { all, fork } from 'redux-saga/effects';

export default function* rootSaga() {
}
0:

import { all, fork } from 'redux-saga/effects';

export default function* rootSaga() {
}

Giờ sẽ tới file

import 'babel-polyfill'; // Needed for redux-saga es6 generator support
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware, { END } from 'redux-saga';
import { fromJS } from 'immutable';

const sagaMiddleware = createSagaMiddleware();

export default function configStore(initialState = {}, history, rootReducer) {
    const middlewares = [
        sagaMiddleware
    ];

    const enhancers = [
        applyMiddleware(...middlewares)
    ];

    /* eslint-disable no-underscore-dangle */
    const composeEnhancers =
        process.env.APP_ENV !== 'production' &&
        typeof window === 'object' &&
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
            window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
    /* eslint-enable */

    const store = createStore(
        rootReducer(),
        fromJS(initialState),
        composeEnhancers(...enhancers)
    );

    store.runSaga = sagaMiddleware.run;
    store.close = () => store.dispatch(END);

    return store;
}
7

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import {createBrowserHistory} from 'history';
import 'antd/dist/antd.css';
import configStore from '../shared/configStore';
import rootReducer from '../stores/rootReducer';
import rootSaga from '../stores/rootSaga';
import Routes from './routes';


const initialState = {};
const history = createBrowserHistory();
const store = configStore(initialState, history, rootReducer);
store.runSaga(rootSaga);

const App = () => (
    <Provider store={store}>
        <Router history={history}>
            <Routes />
        </Router>
    </Provider>
);


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

Tạo modules phục vụ

Ở trong folder

import { all, fork } from 'redux-saga/effects';

export default function* rootSaga() {
}
2, các bạn hãy tạo 3 file

import { all, fork } from 'redux-saga/effects';

export default function* rootSaga() {
}
3

import React from 'react';
import { Iterable } from 'immutable';
import getDisplayName from 'recompose/getDisplayName';
import { toPairs } from 'lodash';

export default function propsToJS(WrappedComponent) {
    function PropsToJS(wrappedComponentProps) {
        const KEY = 0;
        const VALUE = 1;

        const propsJS = toPairs(wrappedComponentProps)
            .reduce((newProps, wrappedComponentProp) => {
                newProps[wrappedComponentProp[KEY]] = Iterable.isIterable(wrappedComponentProp[VALUE]) // eslint-disable-line
                    ? wrappedComponentProp[VALUE].toJS()
                    : wrappedComponentProp[VALUE];
                return newProps;
            }, {});

        return <WrappedComponent {...propsJS} />;
    }

    PropsToJS.displayName = `PropsToJS${getDisplayName(WrappedComponent)}`;

    return PropsToJS;
}

import { all, fork } from 'redux-saga/effects';

export default function* rootSaga() {
}
4:

import axios from 'axios';

const csrfToken = document.head.querySelector('meta[name=csrf-token]').getAttribute('content');

export default axios.create({
    headers: {
        'X-CSRF-TOKEN': csrfToken
    }
});

import { all, fork } from 'redux-saga/effects';

export default function* rootSaga() {
}
5:

import {toastr} from 'react-redux-toastr';

function handleResponse(response) {
    switch (response.status) {
        case 200:
            toastr.success(response.data.message);
            break;
        case 422:
            const error = response.data[Object.keys(response.data)[1]];
            const message = error[Object.keys(error)[0]][0];
            toastr.error(message);
            break;
        case 400:
            if (typeof response.data.token != 'undefined') {
                window.axios.defaults.headers.common['X-CSRF-TOKEN'] = response.data.token;
            } else {
                toastr.error(typeof response.data == 'string'
                    ? response.data
                    : response.data[Object.keys(response.data)[0]]
                );
            }
            break;
        default:
            toastr.error(response.statusText ? response.statusText : response.data);
            break;
    }
}

export default handleResponse;

Giờ ở trong

import { all, fork } from 'redux-saga/effects';

export default function* rootSaga() {
}
6 chúng ta sẽ tạo file
import { combineReducers } from 'redux-immutable';

export default function rootReducer(asyncReducers) {
}
3.

use App\Http\Requests\Auth\LoginRequest;
use App\Http\Requests\Auth\RegisterRequest;
use App\User;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    use AuthenticatesUsers;
    
    public function login(LoginRequest $request)
    {
        if ($this->attemptLogin($request)) {
            $user = Auth::user();

            return $this->sendLoginResponse($request, $user);
        }

        return $this->sendFailedLoginResponse($request);
    }

    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

    protected function sendLoginResponse(Request $request, $user)
    {
        $request->session()->regenerate();

        $this->clearLoginAttempts($request);

        if ($response = $this->authenticated($request, $this->guard()->user())) {
            return $response;
        }

        return response()->json([
            'data' => $user,
            'message' => 'Đăng nhập thành công',
        ], 200);
    }

    protected function sendFailedLoginResponse()
    {
        return response()->json([
            'message' => 'Email hoặc mật khẩu của bạn không chính xác',
        ], 400);
    }
}
0

Cuối cùng là update lại

import { combineReducers } from 'redux-immutable';

export default function rootReducer(asyncReducers) {
}
4 và
import { combineReducers } from 'redux-immutable';

export default function rootReducer(asyncReducers) {
}
7

import { combineReducers } from 'redux-immutable';

export default function rootReducer(asyncReducers) {
}
9

use App\Http\Requests\Auth\LoginRequest;
use App\Http\Requests\Auth\RegisterRequest;
use App\User;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    use AuthenticatesUsers;
    
    public function login(LoginRequest $request)
    {
        if ($this->attemptLogin($request)) {
            $user = Auth::user();

            return $this->sendLoginResponse($request, $user);
        }

        return $this->sendFailedLoginResponse($request);
    }

    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

    protected function sendLoginResponse(Request $request, $user)
    {
        $request->session()->regenerate();

        $this->clearLoginAttempts($request);

        if ($response = $this->authenticated($request, $this->guard()->user())) {
            return $response;
        }

        return response()->json([
            'data' => $user,
            'message' => 'Đăng nhập thành công',
        ], 200);
    }

    protected function sendFailedLoginResponse()
    {
        return response()->json([
            'message' => 'Email hoặc mật khẩu của bạn không chính xác',
        ], 400);
    }
}
1

import { all, fork } from 'redux-saga/effects';

export default function* rootSaga() {
}
0

use App\Http\Requests\Auth\LoginRequest;
use App\Http\Requests\Auth\RegisterRequest;
use App\User;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    use AuthenticatesUsers;
    
    public function login(LoginRequest $request)
    {
        if ($this->attemptLogin($request)) {
            $user = Auth::user();

            return $this->sendLoginResponse($request, $user);
        }

        return $this->sendFailedLoginResponse($request);
    }

    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

    protected function sendLoginResponse(Request $request, $user)
    {
        $request->session()->regenerate();

        $this->clearLoginAttempts($request);

        if ($response = $this->authenticated($request, $this->guard()->user())) {
            return $response;
        }

        return response()->json([
            'data' => $user,
            'message' => 'Đăng nhập thành công',
        ], 200);
    }

    protected function sendFailedLoginResponse()
    {
        return response()->json([
            'message' => 'Email hoặc mật khẩu của bạn không chính xác',
        ], 400);
    }
}
2

Thiết lập routes

Ở trong

import 'babel-polyfill'; // Needed for redux-saga es6 generator support
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware, { END } from 'redux-saga';
import { fromJS } from 'immutable';

const sagaMiddleware = createSagaMiddleware();

export default function configStore(initialState = {}, history, rootReducer) {
    const middlewares = [
        sagaMiddleware
    ];

    const enhancers = [
        applyMiddleware(...middlewares)
    ];

    /* eslint-disable no-underscore-dangle */
    const composeEnhancers =
        process.env.APP_ENV !== 'production' &&
        typeof window === 'object' &&
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
            window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
    /* eslint-enable */

    const store = createStore(
        rootReducer(),
        fromJS(initialState),
        composeEnhancers(...enhancers)
    );

    store.runSaga = sagaMiddleware.run;
    store.close = () => store.dispatch(END);

    return store;
}
7 bạn sẽ thấy có
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import {createBrowserHistory} from 'history';
import 'antd/dist/antd.css';
import configStore from '../shared/configStore';
import rootReducer from '../stores/rootReducer';
import rootSaga from '../stores/rootSaga';
import Routes from './routes';


const initialState = {};
const history = createBrowserHistory();
const store = configStore(initialState, history, rootReducer);
store.runSaga(rootSaga);

const App = () => (
    <Provider store={store}>
        <Router history={history}>
            <Routes />
        </Router>
    </Provider>
);


ReactDOM.render(<App/>, document.getElementById('app'));
3 được import từ
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import {createBrowserHistory} from 'history';
import 'antd/dist/antd.css';
import configStore from '../shared/configStore';
import rootReducer from '../stores/rootReducer';
import rootSaga from '../stores/rootSaga';
import Routes from './routes';


const initialState = {};
const history = createBrowserHistory();
const store = configStore(initialState, history, rootReducer);
store.runSaga(rootSaga);

const App = () => (
    <Provider store={store}>
        <Router history={history}>
            <Routes />
        </Router>
    </Provider>
);


ReactDOM.render(<App/>, document.getElementById('app'));
4. Giờ chúng ta hãy cùng xem trong đó có gì nhé

index.js:

use App\Http\Requests\Auth\LoginRequest;
use App\Http\Requests\Auth\RegisterRequest;
use App\User;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    use AuthenticatesUsers;
    
    public function login(LoginRequest $request)
    {
        if ($this->attemptLogin($request)) {
            $user = Auth::user();

            return $this->sendLoginResponse($request, $user);
        }

        return $this->sendFailedLoginResponse($request);
    }

    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

    protected function sendLoginResponse(Request $request, $user)
    {
        $request->session()->regenerate();

        $this->clearLoginAttempts($request);

        if ($response = $this->authenticated($request, $this->guard()->user())) {
            return $response;
        }

        return response()->json([
            'data' => $user,
            'message' => 'Đăng nhập thành công',
        ], 200);
    }

    protected function sendFailedLoginResponse()
    {
        return response()->json([
            'message' => 'Email hoặc mật khẩu của bạn không chính xác',
        ], 400);
    }
}
3

Ở đây các bạn sẽ thấy mình có chia ra làm 2 loại routes nư mình nói ở trên

history.js:

use App\Http\Requests\Auth\LoginRequest;
use App\Http\Requests\Auth\RegisterRequest;
use App\User;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    use AuthenticatesUsers;
    
    public function login(LoginRequest $request)
    {
        if ($this->attemptLogin($request)) {
            $user = Auth::user();

            return $this->sendLoginResponse($request, $user);
        }

        return $this->sendFailedLoginResponse($request);
    }

    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

    protected function sendLoginResponse(Request $request, $user)
    {
        $request->session()->regenerate();

        $this->clearLoginAttempts($request);

        if ($response = $this->authenticated($request, $this->guard()->user())) {
            return $response;
        }

        return response()->json([
            'data' => $user,
            'message' => 'Đăng nhập thành công',
        ], 200);
    }

    protected function sendFailedLoginResponse()
    {
        return response()->json([
            'message' => 'Email hoặc mật khẩu của bạn không chính xác',
        ], 400);
    }
}
4

Giờ mình sẽ tạo

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import {createBrowserHistory} from 'history';
import 'antd/dist/antd.css';
import configStore from '../shared/configStore';
import rootReducer from '../stores/rootReducer';
import rootSaga from '../stores/rootSaga';
import Routes from './routes';


const initialState = {};
const history = createBrowserHistory();
const store = configStore(initialState, history, rootReducer);
store.runSaga(rootSaga);

const App = () => (
    <Provider store={store}>
        <Router history={history}>
            <Routes />
        </Router>
    </Provider>
);


ReactDOM.render(<App/>, document.getElementById('app'));
5. Trong folder
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import {createBrowserHistory} from 'history';
import 'antd/dist/antd.css';
import configStore from '../shared/configStore';
import rootReducer from '../stores/rootReducer';
import rootSaga from '../stores/rootSaga';
import Routes from './routes';


const initialState = {};
const history = createBrowserHistory();
const store = configStore(initialState, history, rootReducer);
store.runSaga(rootSaga);

const App = () => (
    <Provider store={store}>
        <Router history={history}>
            <Routes />
        </Router>
    </Provider>
);


ReactDOM.render(<App/>, document.getElementById('app'));
6 trông sẽ như sau:
Hướng dẫn react php authentication - phản ứng xác thực php

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import {createBrowserHistory} from 'history';
import 'antd/dist/antd.css';
import configStore from '../shared/configStore';
import rootReducer from '../stores/rootReducer';
import rootSaga from '../stores/rootSaga';
import Routes from './routes';


const initialState = {};
const history = createBrowserHistory();
const store = configStore(initialState, history, rootReducer);
store.runSaga(rootSaga);

const App = () => (
    <Provider store={store}>
        <Router history={history}>
            <Routes />
        </Router>
    </Provider>
);


ReactDOM.render(<App/>, document.getElementById('app'));
7 sẽ để export 2 file kia ra nên trông rất đơn giản

use App\Http\Requests\Auth\LoginRequest;
use App\Http\Requests\Auth\RegisterRequest;
use App\User;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    use AuthenticatesUsers;
    
    public function login(LoginRequest $request)
    {
        if ($this->attemptLogin($request)) {
            $user = Auth::user();

            return $this->sendLoginResponse($request, $user);
        }

        return $this->sendFailedLoginResponse($request);
    }

    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

    protected function sendLoginResponse(Request $request, $user)
    {
        $request->session()->regenerate();

        $this->clearLoginAttempts($request);

        if ($response = $this->authenticated($request, $this->guard()->user())) {
            return $response;
        }

        return response()->json([
            'data' => $user,
            'message' => 'Đăng nhập thành công',
        ], 200);
    }

    protected function sendFailedLoginResponse()
    {
        return response()->json([
            'message' => 'Email hoặc mật khẩu của bạn không chính xác',
        ], 400);
    }
}
5

Ở trong

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import {createBrowserHistory} from 'history';
import 'antd/dist/antd.css';
import configStore from '../shared/configStore';
import rootReducer from '../stores/rootReducer';
import rootSaga from '../stores/rootSaga';
import Routes from './routes';


const initialState = {};
const history = createBrowserHistory();
const store = configStore(initialState, history, rootReducer);
store.runSaga(rootSaga);

const App = () => (
    <Provider store={store}>
        <Router history={history}>
            <Routes />
        </Router>
    </Provider>
);


ReactDOM.render(<App/>, document.getElementById('app'));
8 sẽ chứa phần để xử lý với store, như việc chuyển state sang props và dispatch các action

use App\Http\Requests\Auth\LoginRequest;
use App\Http\Requests\Auth\RegisterRequest;
use App\User;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    use AuthenticatesUsers;
    
    public function login(LoginRequest $request)
    {
        if ($this->attemptLogin($request)) {
            $user = Auth::user();

            return $this->sendLoginResponse($request, $user);
        }

        return $this->sendFailedLoginResponse($request);
    }

    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

    protected function sendLoginResponse(Request $request, $user)
    {
        $request->session()->regenerate();

        $this->clearLoginAttempts($request);

        if ($response = $this->authenticated($request, $this->guard()->user())) {
            return $response;
        }

        return response()->json([
            'data' => $user,
            'message' => 'Đăng nhập thành công',
        ], 200);
    }

    protected function sendFailedLoginResponse()
    {
        return response()->json([
            'message' => 'Email hoặc mật khẩu của bạn không chính xác',
        ], 400);
    }
}
6

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import {createBrowserHistory} from 'history';
import 'antd/dist/antd.css';
import configStore from '../shared/configStore';
import rootReducer from '../stores/rootReducer';
import rootSaga from '../stores/rootSaga';
import Routes from './routes';


const initialState = {};
const history = createBrowserHistory();
const store = configStore(initialState, history, rootReducer);
store.runSaga(rootSaga);

const App = () => (
    <Provider store={store}>
        <Router history={history}>
            <Routes />
        </Router>
    </Provider>
);


ReactDOM.render(<App/>, document.getElementById('app'));
9 sẽ chứa code xử lý kiểm tra curentUser trong store

use App\Http\Requests\Auth\LoginRequest;
use App\Http\Requests\Auth\RegisterRequest;
use App\User;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    use AuthenticatesUsers;
    
    public function login(LoginRequest $request)
    {
        if ($this->attemptLogin($request)) {
            $user = Auth::user();

            return $this->sendLoginResponse($request, $user);
        }

        return $this->sendFailedLoginResponse($request);
    }

    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

    protected function sendLoginResponse(Request $request, $user)
    {
        $request->session()->regenerate();

        $this->clearLoginAttempts($request);

        if ($response = $this->authenticated($request, $this->guard()->user())) {
            return $response;
        }

        return response()->json([
            'data' => $user,
            'message' => 'Đăng nhập thành công',
        ], 200);
    }

    protected function sendFailedLoginResponse()
    {
        return response()->json([
            'message' => 'Email hoặc mật khẩu của bạn không chính xác',
        ], 400);
    }
}
7

Với guest-route thì cũng tương tự, chỉ là đổi logic xử lý 1 chút.

Tạo view

Ở trong folder

import React from 'react';
import { Iterable } from 'immutable';
import getDisplayName from 'recompose/getDisplayName';
import { toPairs } from 'lodash';

export default function propsToJS(WrappedComponent) {
    function PropsToJS(wrappedComponentProps) {
        const KEY = 0;
        const VALUE = 1;

        const propsJS = toPairs(wrappedComponentProps)
            .reduce((newProps, wrappedComponentProp) => {
                newProps[wrappedComponentProp[KEY]] = Iterable.isIterable(wrappedComponentProp[VALUE]) // eslint-disable-line
                    ? wrappedComponentProp[VALUE].toJS()
                    : wrappedComponentProp[VALUE];
                return newProps;
            }, {});

        return <WrappedComponent {...propsJS} />;
    }

    PropsToJS.displayName = `PropsToJS${getDisplayName(WrappedComponent)}`;

    return PropsToJS;
}
0, mình sẽ tạo 1 folder
import React from 'react';
import { Iterable } from 'immutable';
import getDisplayName from 'recompose/getDisplayName';
import { toPairs } from 'lodash';

export default function propsToJS(WrappedComponent) {
    function PropsToJS(wrappedComponentProps) {
        const KEY = 0;
        const VALUE = 1;

        const propsJS = toPairs(wrappedComponentProps)
            .reduce((newProps, wrappedComponentProp) => {
                newProps[wrappedComponentProp[KEY]] = Iterable.isIterable(wrappedComponentProp[VALUE]) // eslint-disable-line
                    ? wrappedComponentProp[VALUE].toJS()
                    : wrappedComponentProp[VALUE];
                return newProps;
            }, {});

        return <WrappedComponent {...propsJS} />;
    }

    PropsToJS.displayName = `PropsToJS${getDisplayName(WrappedComponent)}`;

    return PropsToJS;
}
1 có cấu trúc như sau
Hướng dẫn react php authentication - phản ứng xác thực php

Mình sẽ chỉ đi vào những file quan trọng thôi nhé:

import { combineReducers } from 'redux-immutable';

export default function rootReducer(asyncReducers) {
}
3

use App\Http\Requests\Auth\LoginRequest;
use App\Http\Requests\Auth\RegisterRequest;
use App\User;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    use AuthenticatesUsers;
    
    public function login(LoginRequest $request)
    {
        if ($this->attemptLogin($request)) {
            $user = Auth::user();

            return $this->sendLoginResponse($request, $user);
        }

        return $this->sendFailedLoginResponse($request);
    }

    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

    protected function sendLoginResponse(Request $request, $user)
    {
        $request->session()->regenerate();

        $this->clearLoginAttempts($request);

        if ($response = $this->authenticated($request, $this->guard()->user())) {
            return $response;
        }

        return response()->json([
            'data' => $user,
            'message' => 'Đăng nhập thành công',
        ], 200);
    }

    protected function sendFailedLoginResponse()
    {
        return response()->json([
            'message' => 'Email hoặc mật khẩu của bạn không chính xác',
        ], 400);
    }
}
8

Như các bạn đã thấy ở đây mình đã sử dụng hook để quản lý tate thay vì cách cũ, code đã ngắn đi rất nhiều. Ở đây thì mình sẽ làm view đăng nhập và đăng ký chung 1 địa chỉ đường dẫn. Bạn có thể tách ra hoặc làm giống mình, đó là tùy ở quyết định của bạn.

import React from 'react';
import { Iterable } from 'immutable';
import getDisplayName from 'recompose/getDisplayName';
import { toPairs } from 'lodash';

export default function propsToJS(WrappedComponent) {
    function PropsToJS(wrappedComponentProps) {
        const KEY = 0;
        const VALUE = 1;

        const propsJS = toPairs(wrappedComponentProps)
            .reduce((newProps, wrappedComponentProp) => {
                newProps[wrappedComponentProp[KEY]] = Iterable.isIterable(wrappedComponentProp[VALUE]) // eslint-disable-line
                    ? wrappedComponentProp[VALUE].toJS()
                    : wrappedComponentProp[VALUE];
                return newProps;
            }, {});

        return <WrappedComponent {...propsJS} />;
    }

    PropsToJS.displayName = `PropsToJS${getDisplayName(WrappedComponent)}`;

    return PropsToJS;
}
3

use App\Http\Requests\Auth\LoginRequest;
use App\Http\Requests\Auth\RegisterRequest;
use App\User;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    use AuthenticatesUsers;
    
    public function login(LoginRequest $request)
    {
        if ($this->attemptLogin($request)) {
            $user = Auth::user();

            return $this->sendLoginResponse($request, $user);
        }

        return $this->sendFailedLoginResponse($request);
    }

    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

    protected function sendLoginResponse(Request $request, $user)
    {
        $request->session()->regenerate();

        $this->clearLoginAttempts($request);

        if ($response = $this->authenticated($request, $this->guard()->user())) {
            return $response;
        }

        return response()->json([
            'data' => $user,
            'message' => 'Đăng nhập thành công',
        ], 200);
    }

    protected function sendFailedLoginResponse()
    {
        return response()->json([
            'message' => 'Email hoặc mật khẩu của bạn không chính xác',
        ], 400);
    }
}
9

import React from 'react';
import { Iterable } from 'immutable';
import getDisplayName from 'recompose/getDisplayName';
import { toPairs } from 'lodash';

export default function propsToJS(WrappedComponent) {
    function PropsToJS(wrappedComponentProps) {
        const KEY = 0;
        const VALUE = 1;

        const propsJS = toPairs(wrappedComponentProps)
            .reduce((newProps, wrappedComponentProp) => {
                newProps[wrappedComponentProp[KEY]] = Iterable.isIterable(wrappedComponentProp[VALUE]) // eslint-disable-line
                    ? wrappedComponentProp[VALUE].toJS()
                    : wrappedComponentProp[VALUE];
                return newProps;
            }, {});

        return <WrappedComponent {...propsJS} />;
    }

    PropsToJS.displayName = `PropsToJS${getDisplayName(WrappedComponent)}`;

    return PropsToJS;
}
4 mình đã
import React from 'react';
import { Iterable } from 'immutable';
import getDisplayName from 'recompose/getDisplayName';
import { toPairs } from 'lodash';

export default function propsToJS(WrappedComponent) {
    function PropsToJS(wrappedComponentProps) {
        const KEY = 0;
        const VALUE = 1;

        const propsJS = toPairs(wrappedComponentProps)
            .reduce((newProps, wrappedComponentProp) => {
                newProps[wrappedComponentProp[KEY]] = Iterable.isIterable(wrappedComponentProp[VALUE]) // eslint-disable-line
                    ? wrappedComponentProp[VALUE].toJS()
                    : wrappedComponentProp[VALUE];
                return newProps;
            }, {});

        return <WrappedComponent {...propsJS} />;
    }

    PropsToJS.displayName = `PropsToJS${getDisplayName(WrappedComponent)}`;

    return PropsToJS;
}
5 action
import React from 'react';
import { Iterable } from 'immutable';
import getDisplayName from 'recompose/getDisplayName';
import { toPairs } from 'lodash';

export default function propsToJS(WrappedComponent) {
    function PropsToJS(wrappedComponentProps) {
        const KEY = 0;
        const VALUE = 1;

        const propsJS = toPairs(wrappedComponentProps)
            .reduce((newProps, wrappedComponentProp) => {
                newProps[wrappedComponentProp[KEY]] = Iterable.isIterable(wrappedComponentProp[VALUE]) // eslint-disable-line
                    ? wrappedComponentProp[VALUE].toJS()
                    : wrappedComponentProp[VALUE];
                return newProps;
            }, {});

        return <WrappedComponent {...propsJS} />;
    }

    PropsToJS.displayName = `PropsToJS${getDisplayName(WrappedComponent)}`;

    return PropsToJS;
}
6 để gọi tới api thực hiện việc thêm người dùng. Và View đăng nhập thì cũng làm gần như tương tự nha.

Bài viết này hôm nay sẽ kết thục tại đây, các bạn có gì góp ý thì hãy để lại comment cho mình nhé. Hẹn gặp bạn lại ở bài viết tiếp theo