I try to use laravel 8 Sanctum in my vuejs 2 Spa app and I created 2 app. But running request from SPA page :
axios.get('/sanctum/csrf-cookie').then(response => {
console.log('response::')
console.log(response)
})
I got returned:
status: 204, statusText: "No Content"
I followed some articles like : https://blog.codecourse.com/setting-up-laravel-sanctum-airlock-for-spa-authentication-with-vue/
and seems I followed in backend app, as :
In .env I added/modified lines :
SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost
SESSION_DRIVER=cookie
i run client on http://localhost:8080/ host
In /routes/api.php I modified :
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
//Route::middleware('auth:api')->get('/user', function (Request $request) {
// return $request->user();
//});
In config/cors.php :
'paths' => [
'api/*',
'/login',
'/logout',
'sanctum/csrf-cookie'
],
'supports_credentials' => true,
In config/sanctum.php :
'stateful' => explode(',', env(
'SANCTUM_STATEFUL_DOMAINS',
'localhost:8080,localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1,'.parse_url(env('APP_URL'), PHP_URL_HOST)
)),
...
'middleware' => [
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
],
I modified app/Http/Kernel.php :
protected $middlewareGroups = [
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'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,
],
];
Seacrhing in net for decisons I found several branches with decision,like SPA vue frontend and Laravel 7 backend Sanctum
I tried to modify file app/Providers/RouteServiceProvider.php, but originally it was different as mentioned in the branch above :
<?php
namespace App\Providers;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
/**
* The path to the "home" route for your application.
*
* This is used by Laravel authentication to redirect users after login.
*
* @var string
*/
public const HOME = '/home';
/**
* The controller namespace for the application.
*
* When present, controller route declarations will automatically be prefixed with this namespace.
*
* @var string|null
*/
// protected $namespace = 'App\\Http\\Controllers';
/**
* Define your route model bindings, pattern filters, etc.
*
* @return void
*/
public function boot()
{
$this->configureRateLimiting();
$this->routes(function () {
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
});
}
/**
* Configure the rate limiters for the application.
*
* @return void
*/
protected function configureRateLimiting()
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip());
});
}
}
So it does not have mapApiRoutes method and I tried in 2 way :
public function boot()
{
$this->configureRateLimiting();
$this->routes(function () {
/* Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));*/
Route::prefix('api')
->middleware('web')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
});
}
// Or added mapApiRoutes function
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('web')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
But It did not help and I still got 204 error.
What is wrong ?
In my composer.json :
"require": {
"php": "^7.3|^8.0",
"fideloper/proxy": "^4.4",
"fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^7.0.1",
"laravel/framework": "^8.12",
"laravel/sanctum": "^2.10",
"laravel/tinker": "^2.5",
"laravel/ui": "^2.0"
},
MODIFIED BLOCK : Searching how to fix the issue I payed attention that : 1)I did not find file app/Providers/SanctumServiceProvider.php, even I run command
Project$ php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
Copied Directory [/vendor/laravel/sanctum/database/migrations] To [/database/migrations]
Publishing complete.
So I manually copied file from vendor/laravel/sanctum/src/SanctumServiceProvider.php
into app/Providers/SanctumServiceProvider.php and it has content :
<?php
namespace Laravel\Sanctum;
use Illuminate\Auth\RequestGuard;
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
use Laravel\Sanctum\Http\Controllers\CsrfCookieController;
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
class SanctumServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
config([
'auth.guards.sanctum' => array_merge([
'driver' => 'sanctum',
'provider' => null,
], config('auth.guards.sanctum', [])),
]);
if (! $this->app->configurationIsCached()) {
$this->mergeConfigFrom(__DIR__.'/../config/sanctum.php', 'sanctum');
}
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
if ($this->app->runningInConsole()) {
$this->registerMigrations();
$this->publishes([
__DIR__.'/../database/migrations' => database_path('migrations'),
], 'sanctum-migrations');
$this->publishes([
__DIR__.'/../config/sanctum.php' => config_path('sanctum.php'),
], 'sanctum-config');
}
$this->defineRoutes();
$this->configureGuard();
$this->configureMiddleware();
}
/**
* Register Sanctum's migration files.
*
* @return void
*/
protected function registerMigrations()
{
if (Sanctum::shouldRunMigrations()) {
return $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}
}
/**
* Define the Sanctum routes.
*
* @return void
*/
protected function defineRoutes()
{
if ($this->app->routesAreCached() || config('sanctum.routes') === false) {
return;
}
Route::group(['prefix' => config('sanctum.prefix', 'sanctum')], function () {
Route::get(
'/csrf-cookie',
CsrfCookieController::class.'@show'
)->middleware('web');
});
}
/**
* Configure the Sanctum authentication guard.
*
* @return void
*/
protected function configureGuard()
{
Auth::resolved(function ($auth) {
$auth->extend('sanctum', function ($app, $name, array $config) use ($auth) {
return tap($this->createGuard($auth, $config), function ($guard) {
app()->refresh('request', $guard, 'setRequest');
});
});
});
}
/**
* Register the guard.
*
* @param \Illuminate\Contracts\Auth\Factory $auth
* @param array $config
* @return RequestGuard
*/
protected function createGuard($auth, $config)
{
return new RequestGuard(
new Guard($auth, config('sanctum.expiration'), $config['provider']),
$this->app['request'],
$auth->createUserProvider($config['provider'] ?? null)
);
}
/**
* Configure the Sanctum middleware and priority.
*
* @return void
*/
protected function configureMiddleware()
{
$kernel = $this->app->make(Kernel::class);
$kernel->prependToMiddlewarePriority(EnsureFrontendRequestsAreStateful::class);
}
}
I see at method defineRoutes whioch has defined :
Route::get(
'/csrf-cookie',
CsrfCookieController::class.'@show'
)->middleware('web');
So I try to debug this method with logging, like :
class CsrfCookieController
{
/**
* Return an empty response simply to trigger the storage of the CSRF cookie in the browser.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function show(Request $request)
{
\Log::info( '-1 show.CsrfCookieController ::' . print_r( -1, true ) );
if ($request->expectsJson()) {
\Log::info( '-2 show.CsrfCookieController ::' . print_r( -2, true ) );
return new JsonResponse(null, 204);
}
\Log::info( '-3 show.CsrfCookieController ::' . print_r( -3, true ) );
return new Response('', 204);
}
and in file laravel.log I see output of “-1” and “-2” lines.
Also my responce output has lines :
Request URL: http://local-vsanc-backend-api.com/sanctum/csrf-cookie
Request Method: GET
Status Code: 204 No Content
Remote Address: 127.0.0.25:80
Referrer Policy: strict-origin-when-cross-origin
http://local-vsanc-backend-api.com - is host of my backend app which is writen in file /etc/hosts(on my local kubuntu 18) as :
127.0.0.1 localhost
127.0.1.1 AtHome
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
...
127.0.0.25 local-vsanc-backend-api.com
I suppose that my both apps frontend are on the same domain...
Can it be the issue ?
MODIFIED BLOCK # 2: I tried as :
axios.get('/sanctum/csrf-cookie', { withCredentials: true }).then(response => {
console.log('response::')
console.log(response)
})
also I tried another similar way I used with tymon/jwt-auth:
let settingCredentialsConfig = {
withCredentials:true,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials':true
}
}
console.log('settingCredentialsConfig::')
console.log(settingCredentialsConfig)
axios.get('/sanctum/csrf-cookie', settingCredentialsConfig).then(response => {
console.log('response::')
console.log(response)
})
}
But in both cases 204 No content respoce. It seems to me that something is wrong on server side...
MODIFIED BLOCK # 3 : If I try to make login request afer I got 204 in responce of /sanctum/csrf-cookie I got next 419 error in client code :
login() {
axios.get('/sanctum/csrf-cookie').then(response => {
console.log('response:::')
console.log(response)
axios.post('/login', {
email: this.email,
password: this.password,
}).then(response2 => {
console.log('response2:::')
console.log(response2);
...
}).catch(error => {
console.log(error);
})
})
}
I suppose because of csrf was not set in my app https://prnt.sc/127b8pl Have I to set it manually somehow or some missing packages? package.json of my app :
{
"name": "vsanc",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.21.1",
"core-js": "^3.6.5",
"vue": "^2.6.11"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
Opening one of my Laravel 8 app I see cookies are set : https://prnt.sc/12aarn0
Why I have no csrf applied in cookie ?
Thanks!