Christian
Christian

Reputation: 1853

Laravel/React: barryvdh/laravel-cors handling preflight HTTP OPTIONS request

Just getting started with barryvdh/laravel-cors. I have a server side application running on port 8000 which authenticates users via Laravel Passport, and my react.js client application running on port 3000. Right now I'm trying to get a "login" action working.

From the client when I provide correct credentials a token is sent back and the status is 200. When the credentials are invalid a 401 is returned. So good so far. However, if I provide only an email in the form and no password I get the following CORS related error:

errors

When the error is thrown, the following network calls occur:

enter image description here

My login on the client side:

login = (email, password) => {
    console.log(email);
    fetch("http://localhost:8000/api/auth/login", {
        method: 'POST',
        body: JSON.stringify({
            'email': email,
            'password': password
        }),
        headers: {
            'Content-Type': 'application/json'
        }
    })
    .then(res => res.json())
    .then(response => {
        console.log(response['access_token']); //with correct credentials this is logged out and set in local storage
        localStorage.setItem('access_token', response['access_token']);
    })
    .catch(error => console.error('Error: ', error));
}

Server side:

public function login(Request $request)
{
    $request->validate([
        'email' => 'required|string|email',
        'password' => 'required|string',
        'remember_me' => 'boolean'
    ]);

    $credentials = request(['email', 'password']);

    if(!Auth::attempt($credentials)) {
        return response()->json([
            'message' => 'Unauthorized'
        ], 401);
    }

    $user = $request->user();
    $tokenResult = $user->createToken('Personal Access Token');
    $token = $tokenResult->token;

    if ($request->remember_me) {
        $token->expires_at = Carbon::now()->addWeeks(1);
    }

    $token->save();
    return response()->json([
        'access_token' => $tokenResult->accessToken,
        'token_type' => 'Bearer',
        'expires_at' => Carbon::parse(
            $tokenResult->token->expires_at
        )->toDateTimeString()
    ]);
}

cors.php (published from barryvdh/laravel-cors package):

return [
    'supportsCredentials' => false,
    'allowedOrigins' => ['*'],
    'allowedOriginsPatterns' => [],
    'allowedHeaders' => ['*'],
    'allowedMethods' => ['*'],
    'exposedHeaders' => [],
    'maxAge' => 0,
];

api.php:

Route::group(['prefix' => 'auth',], function () {
    Route::post('login', 'AuthController@login');
    Route::post('register', 'AuthController@register');
    Route::get('register/activate/{token}', 'AuthController@registerActivate');
    Route::group(['middleware' => 'auth:api'], function() {
        Route::get('logout', 'AuthController@logout');
        Route::get('user', 'AuthController@user');
    });
});

Kernel.php:

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        // \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],

    'api' => [
        \Barryvdh\Cors\HandleCors::class,
        'throttle:60,1',
        'bindings',
    ],
];

protected $middleware = [
    \Barryvdh\Cors\HandleCors::class,
    \App\Http\Middleware\CheckForMaintenanceMode::class,
    \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
    \App\Http\Middleware\TrimStrings::class,
    \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    \App\Http\Middleware\TrustProxies::class
];

Making the same POST request (one to /api/auth/login with the password excluded) via postman I get the expected error message:

{
    "message": "The given data was invalid.",
    "errors": {
        "password": [
            "The password field is required."
        ]
    }
}

But, making this through the react client application the error in the screenshot is thrown. I'm guessing this has something to do with the preflight HTTP OPTIONS request that the browser sends out as part of how CORS works.

How can I correct this? I'd expect a 401 to be returned for the status and for the react client to get the same JSON message that Postman receives in the case that the password is empty. It just seems weird that based on the payload for that POST request, a CORS related error occurs. Does this mean that the server and/or client is not setting up CORS correctly?

Upvotes: 0

Views: 1719

Answers (2)

Abdelkarim EL AMEL
Abdelkarim EL AMEL

Reputation: 1533

It seems that the error is thrown when an error occurs in your backend.

As mentioned in the docs :

When an error occurs, the middleware isn't run completely. So when this happens, you won't see the actual result, but will get a CORS error instead.

This issue helped figuring the source of the error

Upvotes: 3

loic.lopez
loic.lopez

Reputation: 2103

First of all the class member:

protected $middleware

Will apply the middleware for both api and web

From laravel.com/docs/5.8/middleware#registering-middleware

If you want a middleware to run during every HTTP request to your application, list the middleware class in the $middleware property of your app/Http/Kernel.php class.

See also: barryvdh/laravel-cors#global-usage

So try to register your middleware one time by removing: \Barryvdh\Cors\HandleCors::class of your 'api' middleware group.

And for logical purposes refactor your protected $middleware with:

protected $middleware = [
    \App\Http\Middleware\CheckForMaintenanceMode::class,
    \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
    \App\Http\Middleware\TrimStrings::class,
    \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    \App\Http\Middleware\TrustProxies::class,
    \Barryvdh\Cors\HandleCors::class,
];

Upvotes: 1

Related Questions