Hello everybody,
I am having trouble to solve this issue: I don't get SPA auth working because of CSRF.
First of all, I am using httpie to mock requests.
This is how I get the csrf-cookie:
response=$(http -v GET http://api.exampletest/sanctum/csrf-cookie)
xsrf_token=$(echo "$response" | grep -o 'XSRF-TOKEN=[^;]*' | cut -d'=' -f2)
This works:
echo $xsrf_token
eyJpdiI6Ik5xeklxTjBLQVl4bU9wdGNWV0M1dlE9PSIsInZhbHVlIjoieFU5blhZTU5IVEVMMHVEd01zalhuSVdyWmlRTnNzVnVMMUpYMU11RTVhU2ZsL3FacUVuaDE2L1BuYk1GZ0xSaXpTbURkaHdZb1RteTkzRU1CRzluSU1hZWJoZUNOTXFKOTRhUk8wVTkva21xMXV6QlFkSGJUeTlvQ0NjM2pQUSsiLCJtYWMiOiJkNWM5YmI4MDg2NDJiZTk3NTg0ZDUwYzFkZGZiZTZmZDMwZGIzYmE5OGQ0ZDZiODYxNGM5YmNlMzEwZGZiNjg4IiwidGFnIjoiIn0%3D
Now I should eb able to send something to /login, right?
http POST 'http://api.example.test/login' \
'Accept-Language':'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7' \
'Connection':'keep-alive' \
'DNT':'1' \
'Origin':'http://api.example.test' \
'Referer':'http://api.example.test/login' \
'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36' \
'X-XSRF-TOKEN':"$xsrf_token" \
email='[email protected]' password='password'
HTTP/1.1 419 unknown status
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://api.example.test
Cache-Control: no-cache, private
Connection: keep-alive
Content-Type: application/json
Date: Tue, 01 Oct 2024 18:26:35 GMT
Server: nginx/1.25.4
Set-Cookie: laravel_session=eyJpdiI6Ikpjb3RLejY2SG94dlFyNVVwSUtlbkE9PSIsInZhbHVlIjoiUFZiNEVhRXBoRHVWRW55Y2dEUThhOGpyeUdEUm1Da3U5dkJxZXBkWU8vZWtqV2M3cWQ3RERaMzVGMnNMRnROWGdrWmRYYTBWNmZESzBIQkNZUmRWS0RzTnlCc3RqY2xUT2xBL1hJOWNpdHB6WDVEVGFxdm9haUhzR3J5amt2cWkiLCJtYWMiOiI3NmYwYjIxMzY4YmMzYzE0YzdhYWNkMTgyMjQ3ZWJjYzI5NmIzMTJkZjQxOWIyYTJiNmJlNzZjMDRmMGZiNzg4IiwidGFnIjoiIn0%3D; expires=Tue, 01 Oct 2024 20:26:35 GMT; Max-Age=7200; path=/; domain=.example.test; httponly; samesite=lax
Transfer-Encoding: chunked
Vary: Origin
X-Powered-By: PHP/8.3.11
{
"exception": "Symfony\\Component\\HttpKernel\\Exception\\HttpException",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php",
"line": 639,
"message": "CSRF token mismatch.",
"trace": [
{
"class": "Illuminate\\Foundation\\Exceptions\\Handler",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php",
"function": "prepareException",
"line": 582,
"type": "->"
},
{
"class": "Illuminate\\Foundation\\Exceptions\\Handler",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php",
"function": "render",
"line": 51,
"type": "->"
},
{
"class": "Illuminate\\Routing\\Pipeline",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"function": "handleException",
"line": 188,
"type": "->"
},
{
"class": "Illuminate\\Pipeline\\Pipeline",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php",
"function": "Illuminate\\Pipeline\\{closure}",
"line": 49,
"type": "->"
},
{
"class": "Illuminate\\View\\Middleware\\ShareErrorsFromSession",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"function": "handle",
"line": 183,
"type": "->"
},
{
"class": "Illuminate\\Pipeline\\Pipeline",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php",
"function": "Illuminate\\Pipeline\\{closure}",
"line": 121,
"type": "->"
},
{
"class": "Illuminate\\Session\\Middleware\\StartSession",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php",
"function": "handleStatefulRequest",
"line": 64,
"type": "->"
},
{
"class": "Illuminate\\Session\\Middleware\\StartSession",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"function": "handle",
"line": 183,
"type": "->"
},
{
"class": "Illuminate\\Pipeline\\Pipeline",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php",
"function": "Illuminate\\Pipeline\\{closure}",
"line": 37,
"type": "->"
},
{
"class": "Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"function": "handle",
"line": 183,
"type": "->"
},
{
"class": "Illuminate\\Pipeline\\Pipeline",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php",
"function": "Illuminate\\Pipeline\\{closure}",
"line": 75,
"type": "->"
},
{
"class": "Illuminate\\Cookie\\Middleware\\EncryptCookies",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"function": "handle",
"line": 183,
"type": "->"
},
{
"class": "Illuminate\\Pipeline\\Pipeline",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"function": "Illuminate\\Pipeline\\{closure}",
"line": 119,
"type": "->"
},
{
"class": "Illuminate\\Pipeline\\Pipeline",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
"function": "then",
"line": 807,
"type": "->"
},
{
"class": "Illuminate\\Routing\\Router",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
"function": "runRouteWithinStack",
"line": 786,
"type": "->"
},
{
"class": "Illuminate\\Routing\\Router",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
"function": "runRoute",
"line": 750,
"type": "->"
},
{
"class": "Illuminate\\Routing\\Router",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
"function": "dispatchToRoute",
"line": 739,
"type": "->"
},
{
"class": "Illuminate\\Routing\\Router",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
"function": "dispatch",
"line": 201,
"type": "->"
},
{
"class": "Illuminate\\Foundation\\Http\\Kernel",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"function": "Illuminate\\Foundation\\Http\\{closure}",
"line": 144,
"type": "->"
},
{
"class": "Illuminate\\Pipeline\\Pipeline",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php",
"function": "Illuminate\\Pipeline\\{closure}",
"line": 21,
"type": "->"
},
{
"class": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php",
"function": "handle",
"line": 31,
"type": "->"
},
{
"class": "Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"function": "handle",
"line": 183,
"type": "->"
},
{
"class": "Illuminate\\Pipeline\\Pipeline",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php",
"function": "Illuminate\\Pipeline\\{closure}",
"line": 21,
"type": "->"
},
{
"class": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php",
"function": "handle",
"line": 51,
"type": "->"
},
{
"class": "Illuminate\\Foundation\\Http\\Middleware\\TrimStrings",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"function": "handle",
"line": 183,
"type": "->"
},
{
"class": "Illuminate\\Pipeline\\Pipeline",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Http/Middleware/ValidatePostSize.php",
"function": "Illuminate\\Pipeline\\{closure}",
"line": 27,
"type": "->"
},
{
"class": "Illuminate\\Http\\Middleware\\ValidatePostSize",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"function": "handle",
"line": 183,
"type": "->"
},
{
"class": "Illuminate\\Pipeline\\Pipeline",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php",
"function": "Illuminate\\Pipeline\\{closure}",
"line": 110,
"type": "->"
},
{
"class": "Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"function": "handle",
"line": 183,
"type": "->"
},
{
"class": "Illuminate\\Pipeline\\Pipeline",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php",
"function": "Illuminate\\Pipeline\\{closure}",
"line": 62,
"type": "->"
},
{
"class": "Illuminate\\Http\\Middleware\\HandleCors",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"function": "handle",
"line": 183,
"type": "->"
},
{
"class": "Illuminate\\Pipeline\\Pipeline",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php",
"function": "Illuminate\\Pipeline\\{closure}",
"line": 58,
"type": "->"
},
{
"class": "Illuminate\\Http\\Middleware\\TrustProxies",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"function": "handle",
"line": 183,
"type": "->"
},
{
"class": "Illuminate\\Pipeline\\Pipeline",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php",
"function": "Illuminate\\Pipeline\\{closure}",
"line": 22,
"type": "->"
},
{
"class": "Illuminate\\Foundation\\Http\\Middleware\\InvokeDeferredCallbacks",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"function": "handle",
"line": 183,
"type": "->"
},
{
"class": "Illuminate\\Pipeline\\Pipeline",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"function": "Illuminate\\Pipeline\\{closure}",
"line": 119,
"type": "->"
},
{
"class": "Illuminate\\Pipeline\\Pipeline",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
"function": "then",
"line": 176,
"type": "->"
},
{
"class": "Illuminate\\Foundation\\Http\\Kernel",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
"function": "sendRequestThroughRouter",
"line": 145,
"type": "->"
},
{
"class": "Illuminate\\Foundation\\Http\\Kernel",
"file": "/Users/ahoi/Entwicklung/Laravel/example/vendor/laravel/framework/src/Illuminate/Foundation/Application.php",
"function": "handle",
"line": 1188,
"type": "->"
},
{
"class": "Illuminate\\Foundation\\Application",
"file": "/Users/ahoi/Entwicklung/Laravel/example/public/index.php",
"function": "handleRequest",
"line": 19,
"type": "->"
},
{
"file": "/Applications/Herd.app/Contents/Resources/valet/server.php",
"function": "require",
"line": 167
}
]
}
I configured cors.php and sanctum.php:
cors.php:
<?php
declare(strict_types=1);
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => ['*'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];
sanctum.php:
<?php
declare(strict_types=1);
use Laravel\Sanctum\Sanctum;
return [
/*
|--------------------------------------------------------------------------
| Stateful Domains
|--------------------------------------------------------------------------
|
| Requests from the following domains / hosts will receive stateful API
| authentication cookies. Typically, these should include your local
| and production domains which access your API via a frontend SPA.
|
*/
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
Sanctum::currentApplicationUrlWithPort()
))),
/*
|--------------------------------------------------------------------------
| Sanctum Guards
|--------------------------------------------------------------------------
|
| This array contains the authentication guards that will be checked when
| Sanctum is trying to authenticate a request. If none of these guards
| are able to authenticate the request, Sanctum will use the bearer
| token that's present on an incoming request for authentication.
|
*/
'guard' => ['web'],
/*
|--------------------------------------------------------------------------
| Expiration Minutes
|--------------------------------------------------------------------------
|
| This value controls the number of minutes until an issued token will be
| considered expired. This will override any values set in the token's
| "expires_at" attribute, but first-party sessions are not affected.
|
*/
'expiration' => null,
/*
|--------------------------------------------------------------------------
| Token Prefix
|--------------------------------------------------------------------------
|
| Sanctum can prefix new tokens in order to take advantage of numerous
| security scanning initiatives maintained by open source platforms
| that notify developers if they commit tokens into repositories.
|
| See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
|
*/
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
/*
|--------------------------------------------------------------------------
| Sanctum Middleware
|--------------------------------------------------------------------------
|
| When authenticating your first-party SPA with Sanctum you may need to
| customize some of the middleware Sanctum uses while processing the
| request. You may change the middleware listed below as required.
|
*/
'middleware' => [
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
],
];
And of course I am setting those values in my .env:
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=true
SESSION_PATH=/
SESSION_DOMAIN=.example.test
SANCTUM_STATEFUL_DOMAINS="web.example.test, .example.test"