Reputation: 1769
I have been struggling with this for a few hours now on how to solve this issue and I just can't seem to be able to get around it for now.
Am building a simple authentication system with a Vue
front-end(created using vue-cli
) and Laravel 5.8
(api) for the backend; to test out the idea of using httponly cookie for authentication and protection of access to certain routes after reading this article. I am using tymondesigns/jwt-auth for authentication instead of laravel passport as used in the article and am also using barryvdh/laravel-cors
package to add CORS (Cross-Origin Resource Sharing) headers support.
Here is my code in the routes/api.php
Route::group(['prefix' => 'auth', 'namespace' => 'Auth'], function () {
Route::post('login', 'AuthController@login');
Route::group(['middleware' => ['auth.api'],], function () {
Route::get('me', 'AuthController@me');
Route::post('logout', 'AuthController@logout');
});
});
And the code for the middleware that am using is as follows in the app/Http/Kernel.php
'auth.api' => [
\App\Http\Middleware\AddAuthTokenHeader::class,
'throttle:60,1',
'bindings',
'auth:api',
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
],
And here is my code in the app/Http/Controllers/Auth/AuthController.php
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Requests\Auth\LoginRequest;
use App\Http\Controllers\Controller;
class AuthController extends Controller
{
/**
* Authenticate user via given credentials.
*
* @param \App\Http\Requests\Auth\LoginRequest $request
*
* @return \Illuminate\Http\JsonResponse
*/
public function login(LoginRequest $request)
{
$credentials = $request->all(['email', 'password']);
if (!$token = auth()->attempt($credentials)) {
return response()->json(['error' => 'Invalid credentials'], 401);
}
$cookie = $this->getCookie($token);
return response()->json([
'token' => $token,
'user' => auth()->user(),
])->withCookie($cookie);
}
/**
* Set cookie details and return cookie
*
* @param string $token JWT
*
* @return \Illuminate\Cookie\CookieJar|\Symfony\Component\HttpFoundation\Cookie
*/
private function getCookie($token)
{
return cookie(
env('AUTH_COOKIE_NAME'),
$token,
auth()->factory()->getTTL(),
null,
null,
env('APP_DEBUG') ? false : true,
true,
false,
'Strict'
);
}
public function logout()
{
// ...
}
public function me()
{
// ...
}
}
And the code for the handle method in the middleware class(app/Http/Middleware/AddAuthTokenHeader.php
) used in the custom middleware auth.api
is
public function handle($request, Closure $next)
{
$cookie_name = env('AUTH_COOKIE_NAME');
if (!$request->bearerToken()) {
if ($request->hasCookie($cookie_name)) {
$token = $request->cookie($cookie_name);
$request->headers->add([
'Authorization' => 'Bearer ' . $token
]);
}
}
return $next($request);
}
As you can see in my AuthController
once the login request is successfully the json response is sent along with the http-only cookie.
NOTE: Am using php artisan serve
to run my backend
After running npm run serve
in my vue-cli
generated project, I go to the login
route which displays the Login
component represented by @/views/Login.vue
.
Here is my code for the Login.vue
<template>
<div>
<form @submit.prevent="submit" autocomplete="off">
<p>
<label for="email">Email: </label>
<input
type="email"
name="email"
id="email"
v-model.lazy="form.email"
required
autofocus
/>
</p>
<p>
<label for="password">Password: </label>
<input
type="password"
name="password"
id="password"
v-model.lazy="form.password"
required
/>
</p>
<button type="submit">Login</button>
</form>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'login',
data() {
return {
form: {
email: '',
password: '',
},
};
},
methods: {
async submit() {
const url = 'http://localhost:8000/api/auth/login';
const response = await axios.post(url, this.form, {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
console.log(response);
// this.$router.replace({ name: 'home' });
},
},
};
</script>
Given valid credentials on route(http:localhost:8080/login
), the cookie will be returned as seen in the response headers below
but for some reason, it's not being set in the browser cookie cookie storage as shown below
NOTE: The cookie shown above is from me testing if everything is running fine after running php artisan serve
and opening http:localhost:8000
in the browser.
Question is, why isn't the cookie being stored in the browser cookie storage.
I should note that when I call the same backend api routes using POSTMAN
, everything works fine(with the cookie being set on login and cleared on logout).
Thank you.
Upvotes: 4
Views: 11915
Reputation: 177
You can setting httponly=false
for accessing to cookie on front-end.
Laravel provides an option for this:
cookie($name, $value, $minutes = 0, $path = null, $domain = null, $secure = false, $httpOnly = false)
Upvotes: 0
Reputation: 1769
All credit to @skirtle for helping me to solve this; your help was invaluable.
Here are the steps I took to make it work.
I run the command php artisan vendor:publish --provider="Barryvdh\Cors\ServiceProvider"
which creates a file cors.php
under the config
directory.
I then changed this line 'supportsCredentials' => false,
in config/cors.php
to 'supportsCredentials' => true,
and I left everything else the same after which I run the following commands just to make sure the new changes were captured.
php artisan config:clear
php artisan cache:clear
php artisan serve
All I had is to change my submit
method in the Login.vue
to this
async submit() {
const url = 'http://localhost:8000/api/auth/login';
const response = await axios.post(url, this.form, {
withCredentials: true,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
console.log(response.data);
// this.$router.replace({ name: 'home' });
},
And now the cookie is being set as shown below
Once again, all thanks and credit to @skirtle the tips.
Upvotes: 9