Article Laravel Sanctum Fortify

Laravel Sanctum and Fortify guide

Up and running with Laravel Sanctum and Fortify. Basics for API tokens and SPA authentication

⚠️ Note that i would recommend using Laravel Breeze that does exactly the same thing. But if you are interested how breeze is structured you can continue reading :)

In this guide we are going to see the steps needed for being able to get up and running with Sanctum and Fortify. So what are and what do Sanctum and Fortify do?

Just want to check out the code? It exist here laravel-sanctum-fortify 👀

Summary

Sanctum: Provides the authentication for your application. It works in two ways.

  1. API tokens. You get the ability to generate API tokens to verify users. The tokens are stored in the database and authenticate incoming HTTP requests via the Authorization header.
  2. SPA Authentication. Make use of Laravel's build-in cookie-based session authentication services. Gives the benefits of CSRF protection, session authentication and XSS.

How it works is that Laravel will first check for the authentication cookie and if none exist it will check for the API token to know if the user is authenticated.

Fortify

Sanctum only handles the managing of API tokens and authentication of session cookies or tokens. Fortify provides the routes and logic needed for login, user registration, password recovery e.t.c.

Base Laravel install

First, we need a basic laravel installation.

laravel new sanctum-fortify-tutorial
cd sanctum-fortify-tutorial
git init
git add .
git commit -m "init"

Here you can also take advantage of the Laravel built-in support for git. laravel new sanctum-fortify-tutorial --git. You also have support to directly create a GitHub repository together with the GitHub CLI!

If laravel does not exist. You need to install the installer

Next, we need to connect to our Database.

Update the .env to match your database.

After we have configured our database we can run migrate to initialize all required tables.

$ php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (32.55ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (17.98ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (22.27ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (46.57ms)

To start the application we can run php artisan serve. Now we are ready to begin the Sanctum installation.

Sanctum install

More information can be found at the Sanctum documentation A new installation of Laravel comes with Sanctum out of the box!

Update env

SANCTUM_STATEFUL_DOMAINS=localhost:8080,localhost:8000
SESSION_DOMAIN=localhost

Update app/Http/Kernel.php to include Sanctum middleware

'api' => [
            \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

API token

We start by setting up two middleware that is used to authenticate the request token. This is done in the app/Http/Kernel.php

'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
  • abilities can be used to verify that a request have all of the listed abilities.
  • ability can be used to verify that a request has at least one of the listed abilities.

Now to test this we need a user. We do not have any endpoints or views to create a user but we can make use of Tinker.

We can create a user by using the factory method. After that, we can create a token for the user. The created token will then be shown in the console.

php artisan tinker
>>> User::factory(1)->create(['name'=>'Luke Skywalker', 'email'=>'luke@jedi.com', 'password'=>Hash::make('luke')])
>>> User::find(1)->createToken('server-admin', ['server:update'])->plainTextToken
"1|3Oovymkti8w9Ac61tdHhK2mytDNiBPw27DL4fONg"

That token is now stored hashed in our database. The server:update is one of the abilities that can be verified against.

If we update our routes/api.php file to validate against this ability.

Route::middleware(['auth:sanctum', 'ability:server:update'])->get('/user', function (Request $request) {
    return $request->user();
});

If we try and call that endpoint now we will get a 401 Unathenticated. I'm using curl here but I recommend you use e.g. Postman or Insomnia. You will notice later why i recommend that :)

curl -H "Accept: application/json"  http://localhost:8000/api/users
{"message":"Unauthenticated."}

That's because we are not providing the token we created earlier. So we need to pass this in the Authorization header.

curl -H "Accept: application/json" -H "Authorization: Bearer 1|3Oovymkti8w9Ac61tdHhK2mytDNiBPw27DL4fONg" http://localhost:8000/api/user
{"id":1,"name":"Luke Skywalker","email":"luke@jedi.com","email_verified_at":"2022-01-27T20:27:22.000000Z","created_at":"2022-01-27T20:27:22.000000Z","updated_at":"2022-01-27T20:27:22.000000Z"}

And it works! If we create another user and try to access the same endpoint it will not work because we are missing the correct token.

Some troubleshooting if it does not work

  • ⁉️ Verify you are calling the correct endpoint
  • ⁉️ Verify you are using the correct Authorization token
  • ⁉️ Verify you have the correct ability set on the endpoint matching the token abilities.

Now when we have working access to our API by using API tokens we can step over to set up our SPA token.

SPA token

First, we need to make sure that our responses will return the Access-Control-Allow-Credentials. This is so we expose the response to the frontend JavaScript code.

This is done in the config/cors.php file.

'supports_credentials' => true,

When we installed Sanctum we got one route installed sanctum/csrf-cookie. This route will create a token that will be used to verify upcoming requests to prevent Cross-Site-Request Forgery.

If we request the server

curl -H "Accept: application/json" -H "Access-Control-Allow-Credentials: true" http://localhost:8000/sanctum/csrf-cookie

We will not get back any response... Hmm. That is because the endpoint uses the Set-Cookie response to populate the XSRF-TOKEN that we need for upcoming requests. We do not have any login yet so to test this out we can make a basic authentication endpoint.

We can add a new endpoint api/sanctum/token that will validate the user input and create a new user token that we can provide for our Authorization header.

Route::post('/sanctum/token', function (Request $request) {
    $request->validate([
        'email' => 'required|email',
        'password' => 'required',
        'device_name' => 'required',
    ]);

    $user = User::where('email', $request->email)->first();
    if (!$user || !Hash::check($request->password, $user->password)) {
        throw ValidationException::withMessages([
            'email' => ['The provided credentials are incorrect.'],
        ]);
    }

    return $user->createToken($request->device_name)->plainTextToken;
});

If we try and make a request now we will get an "message": "CSRF token mismatch." because we are not providing our CSRF token. "message": "CSRF token mismatch.",

Let's rerun our command but with the -v flag so we can see the response headers.

curl -H "Accept: application/json" -H "Access-Control-Allow-Credentials: true" http://localhost:8000/sanctum/csrf-cookie -v
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8000 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET /sanctum/csrf-cookie HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.64.1
> Accept: application/json
> Access-Control-Allow-Credentials: true
>
< HTTP/1.1 204 No Content
< Host: localhost:8000
< Date: Fri, 28 Jan 2022 19:36:39 GMT
< Connection: close
< X-Powered-By: PHP/7.3.24-(to be removed in future macOS)
< Cache-Control: no-cache, private
< Date: Fri, 28 Jan 2022 19:36:39 GMT
< Vary: Origin
< Set-Cookie: XSRF-TOKEN=eyJpdiI6ImxNcnBodkpXQXFLMmZCbWVwc2FvZGc9PSIsInZhbHVlIjoiYndpdzlmYW04elNoUWlwaENBSWYyeWEzMHhxQW1SR1phUUlNQjRqM294aThpbGhLbHlhcnBOeWhzVGhESW5BS3k3d3BlQnVqcUtjbEJiSlVIbm1hWFRqK0dVcnVzYkJmY2o4U1BSYkFoWm8vMVRseTZySWVnZWxGZFpaZlZQWDEiLCJtYWMiOiIzNzhmODkzNThlNzQ1ZDgxZjQwNWZhMzliYjA0NWRmMzBmZjEwYTM2MTM2YmRlMTI5ZmI0YTRlNzQ5MjJkYThiIiwidGFnIjoiIn0%3D; expires=Fri, 28-Jan-2022 21:36:39 GMT; Max-Age=7200; path=/; domain=localhost; samesite=lax
< Set-Cookie: laravel_session=eyJpdiI6IkhiLzhzUTB4d2liaU1JOGplZjNLRGc9PSIsInZhbHVlIjoiR1NEd1R1bFBHSVhLdUVDMkcvdkl2d0ptNkNCS2FBQzZFNk1OT1JiRzFlMHE0NG84ZEdhVlgzVEhnUGxqQWJLcjZ4dnc3RE9QRm9KQjVLLzNxcXdrazlCdGlNZVFzMCtmVTJzZHRQTWJLblJ0VW41TVl0UDI5aFJlUmxxZXZzK2MiLCJtYWMiOiI0ZDc5YjEyNDBjOGI4MGQxNTQwNmY1ZDY5MWI5NWIyMjI3YTcwNjg1NTE0NjM1ZTE5YTUyYmRkMjFhMzQ1MmJlIiwidGFnIjoiIn0%3D; expires=Fri, 28-Jan-2022 21:36:39 GMT; Max-Age=7200; path=/; domain=localhost; httponly; samesite=lax
<
* Closing connection 0

There we have the XSRF-TOKEN that we can provide for our X-XSRF-TOKEN header.

curl -X POST \
-H "Accept: application/json" \
-H "Access-Control-Allow-Credentials: true" \
-H "X-XSRF-TOKEN: eyJpdiI6ImxNcnBodkpXQXFLMmZCbWVwc2FvZGc9PSIsInZhbHVlIjoiYndpdzlmYW04elNoUWlwaENBSWYyeWEzMHhxQW1SR1phUUlNQjRqM294aThpbGhLbHlhcnBOeWhzVGhESW5BS3k3d3BlQnVqcUtjbEJiSlVIbm1hWFRqK0dVcnVzYkJmY2o4U1BSYkFoWm8vMVRseTZySWVnZWxGZFpaZlZQWDEiLCJtYWMiOiIzNzhmODkzNThlNzQ1ZDgxZjQwNWZhMzliYjA0NWRmMzBmZjEwYTM2MTM2YmRlMTI5ZmI0YTRlNzQ5MjJkYThiIiwidGFnIjoiIn0=" \
-F "email=luke@jedi.com" \
-F "password=luke" \
-F "device_name=curl" \
http://localhost:8000/api/sanctum/token
3|MMseBBcJmLYN78iETh1KAGzToBp3rlvtF8L0GJwI

(Note the change at the end of the cookie %3D is the encoded = char. Need to change that to work correctly.)

Nice looks like everything is working!

Now we have two approaches. We implement all authentication routes ourselves. That is /login, /register, /password-recoveryetc. Or we make use of Laravel Fortify!

Fortify

With the authentication up and running we can introduce Fortify so we do not need to write all authentication logic ourselves.

First, we install the required packages.

composer require laravel/fortify
php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"
php artisan migrate

This will introduce some new fortify actions and configurations. Second, we configure our config/app.php to register the provider.

/*
 * Application Service Providers...
 */
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\FortifyServiceProvider::class,

Now with all controllers and routes up and running we can try and create a new user. First, remember to require a new CSRF token.

curl -H "Accept: application/json" -H "Access-Control-Allow-Credentials: true" http://localhost:8000/sanctum/csrf-cookie -v
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8000 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET /sanctum/csrf-cookie HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.64.1
> Accept: application/json
> Access-Control-Allow-Credentials: true
>
< HTTP/1.1 204 No Content
< Host: localhost:8000
< Date: Sun, 30 Jan 2022 17:17:51 GMT
< Connection: close
< X-Powered-By: PHP/7.3.24-(to be removed in future macOS)
< Cache-Control: no-cache, private
< Date: Sun, 30 Jan 2022 17:17:51 GMT
< Vary: Origin
< Set-Cookie: XSRF-TOKEN=eyJpdiI6ImhXRk13UW5WakcxS1BQYXpZVDJUMEE9PSIsInZhbHVlIjoiZGFpbTlVczdiTTkwaGhRNjY4RnNyU3k5TmhzS3J2Q3dUWld0d3JWT25QT1B5K2hNenA0R25TVG1KM2xKVnNZZGpDT1FqaHZmcUoyM3NnYkZmalZPWkNCcW1hOEZ0TU5kZ1FKWjhqb1dCTEFidVBWellKNTY1cHhVc3lUdU83enQiLCJtYWMiOiJmOGE2ZTMzNmQ0NGU3NjI4N2VkMWE1MTIwNDZmZDg0YWEzNzg2OGE3YzBiZWNiODUzMTA3ZGUzNDcxZWIyMjlmIiwidGFnIjoiIn0%3D; expires=Sun, 30-Jan-2022 19:17:51 GMT; Max-Age=7200; path=/; domain=localhost; samesite=lax
< Set-Cookie: laravel_session=eyJpdiI6IkllZUlySzArOXNSZzVSdkYwTnk5NlE9PSIsInZhbHVlIjoiT3J3S3R1ZzFQdUxIaW5aZjFnNUNtd3lsRWM3TEpHdnh0SFNhTUgxei91dnJsMHRlcDRTckE3QjZhRXlvZ3hhZUlnUVBTRC9IdHYzdjJ3OGdJUW1RUzM4RWFLWGpZRlA2eW9icWIrNXRjTC9KVWROUjVQSU5iZGpGWU9nQW00Z2MiLCJtYWMiOiJkMjY3ZjM5NzVhZTJhYmM0MDFiNjRmZDRjYjVjZGVlY2U5NWViYWMxMzVlNTdhNjEwNGMwMDQ1ZDFmNDAwNjFhIiwidGFnIjoiIn0%3D; expires=Sun, 30-Jan-2022 19:17:51 GMT; Max-Age=7200; path=/; domain=localhost; httponly; samesite=lax
< Set-Cookie: XHwipE64JLQpFn4Fg98nIaAyIXPcd6i1jBYotrov=eyJpdiI6Ik85VEEvRGg3elJRdnRLY2pVSEk1RHc9PSIsInZhbHVlIjoiRHZ6VWxOcFBmQVlPRGNlVUhpcDIzd3RxQ3RRUGtXdENlaE9NS1VwQUw5UEYvSVRVOEFBemMrNWJtaFJsei9vanlybTcrQXNKc2V0REllVW5FMnVRekZQeHcxeXlXSnVYNTI3Q2NwSU1FMUxXZlN4RWdxckJmRk9uZjNzbEpmenRZUk5CTlN2SGRmejI1Y25mVi9VQ290MHlpdU9YandLRWFkeFFCdE9mOGdhVW80bndjQXRVbG1MU2hNRWx6NTNEdldqV0FSTnVoUkd5UHFhSm5WR0JJeXBBUTZwTUdUT3pGTmhCejNubE4xMkcrWDRTYmlhZnE2dFUza3E0M1h4Uk5LRjF1NCsvZk5NQ0NFRjZScVlMMDhYRHpnVWw5cTVxc0pZUktZMEhJdENmbi9YVElOckVYTytBai83NFNmYnplWFlKWmcxbDRRb0x0Ym1UbVJFZTRkcjZNKzZmcklyL3FoSkU2N2Zlc01aZ0Z4SytjUE1ueGpQODUyQjVjNlA1ZWtoeUVYYzZWSWdXSXhHejljcDlEQT09IiwibWFjIjoiZWIwZWUxNzQwYWEyMjcxNDVlNjk2ZTFlODAxZmQzY2E2YzI5ODI0N2QyYWE1MjU2MzliMmFhNmU1OTZjMDcyNSIsInRhZyI6IiJ9; expires=Sun, 30-Jan-2022 19:17:51 GMT; Max-Age=7200; path=/; domain=localhost; httponly; samesite=lax
<
* Closing connection 0

Now we register a new user by using the register endpoint.

curl -X POST \
-H "Accept: application/json" \
-H "Access-Control-Allow-Credentials: true" \
-H "Referer: localhost:8000" \
-H "X-XSRF-TOKEN: eyJpdiI6ImhXRk13UW5WakcxS1BQYXpZVDJUMEE9PSIsInZhbHVlIjoiZGFpbTlVczdiTTkwaGhRNjY4RnNyU3k5TmhzS3J2Q3dUWld0d3JWT25QT1B5K2hNenA0R25TVG1KM2xKVnNZZGpDT1FqaHZmcUoyM3NnYkZmalZPWkNCcW1hOEZ0TU5kZ1FKWjhqb1dCTEFidVBWellKNTY1cHhVc3lUdU83enQiLCJtYWMiOiJmOGE2ZTMzNmQ0NGU3NjI4N2VkMWE1MTIwNDZmZDg0YWEzNzg2OGE3YzBiZWNiODUzMTA3ZGUzNDcxZWIyMjlmIiwidGFnIjoiIn0=" \
-F "name=Han Solo" \
-F "email=han@millennium-falcon.com" \
-F "password=myfalcon" \
-F "password_confirmation=myfalcon" \
http://localhost:8000/register

Hmm, wait a minute. This does not work, I get "message": "CSRF token mismatch.". That's because we are now requesting the web route and not the api route that is protected with CSRF.

The difference with the previous request with api/sanctum/token is that we need to also pass the Cookie header with the combined values from the Set-Cookie headers. If one is using e.g. Postman or Axios. This Cookie header is set automatically. But we are using Curl here so we need to pass it. Let's add that to our Cookie header.

curl -X POST \
-H "Accept: application/json" \
-H "Access-Control-Allow-Credentials: true" \
-H "Referer: localhost:8000" \
-H "X-XSRF-TOKEN: eyJpdiI6ImhXRk13UW5WakcxS1BQYXpZVDJUMEE9PSIsInZhbHVlIjoiZGFpbTlVczdiTTkwaGhRNjY4RnNyU3k5TmhzS3J2Q3dUWld0d3JWT25QT1B5K2hNenA0R25TVG1KM2xKVnNZZGpDT1FqaHZmcUoyM3NnYkZmalZPWkNCcW1hOEZ0TU5kZ1FKWjhqb1dCTEFidVBWellKNTY1cHhVc3lUdU83enQiLCJtYWMiOiJmOGE2ZTMzNmQ0NGU3NjI4N2VkMWE1MTIwNDZmZDg0YWEzNzg2OGE3YzBiZWNiODUzMTA3ZGUzNDcxZWIyMjlmIiwidGFnIjoiIn0=" \
-H "Cookie: XSRF-TOKEN=eyJpdiI6ImhXRk13UW5WakcxS1BQYXpZVDJUMEE9PSIsInZhbHVlIjoiZGFpbTlVczdiTTkwaGhRNjY4RnNyU3k5TmhzS3J2Q3dUWld0d3JWT25QT1B5K2hNenA0R25TVG1KM2xKVnNZZGpDT1FqaHZmcUoyM3NnYkZmalZPWkNCcW1hOEZ0TU5kZ1FKWjhqb1dCTEFidVBWellKNTY1cHhVc3lUdU83enQiLCJtYWMiOiJmOGE2ZTMzNmQ0NGU3NjI4N2VkMWE1MTIwNDZmZDg0YWEzNzg2OGE3YzBiZWNiODUzMTA3ZGUzNDcxZWIyMjlmIiwidGFnIjoiIn0=; XHwipE64JLQpFn4Fg98nIaAyIXPcd6i1jBYotrov=eyJpdiI6Ik85VEEvRGg3elJRdnRLY2pVSEk1RHc9PSIsInZhbHVlIjoiRHZ6VWxOcFBmQVlPRGNlVUhpcDIzd3RxQ3RRUGtXdENlaE9NS1VwQUw5UEYvSVRVOEFBemMrNWJtaFJsei9vanlybTcrQXNKc2V0REllVW5FMnVRekZQeHcxeXlXSnVYNTI3Q2NwSU1FMUxXZlN4RWdxckJmRk9uZjNzbEpmenRZUk5CTlN2SGRmejI1Y25mVi9VQ290MHlpdU9YandLRWFkeFFCdE9mOGdhVW80bndjQXRVbG1MU2hNRWx6NTNEdldqV0FSTnVoUkd5UHFhSm5WR0JJeXBBUTZwTUdUT3pGTmhCejNubE4xMkcrWDRTYmlhZnE2dFUza3E0M1h4Uk5LRjF1NCsvZk5NQ0NFRjZScVlMMDhYRHpnVWw5cTVxc0pZUktZMEhJdENmbi9YVElOckVYTytBai83NFNmYnplWFlKWmcxbDRRb0x0Ym1UbVJFZTRkcjZNKzZmcklyL3FoSkU2N2Zlc01aZ0Z4SytjUE1ueGpQODUyQjVjNlA1ZWtoeUVYYzZWSWdXSXhHejljcDlEQT09IiwibWFjIjoiZWIwZWUxNzQwYWEyMjcxNDVlNjk2ZTFlODAxZmQzY2E2YzI5ODI0N2QyYWE1MjU2MzliMmFhNmU1OTZjMDcyNSIsInRhZyI6IiJ9; laravel_session=eyJpdiI6IkllZUlySzArOXNSZzVSdkYwTnk5NlE9PSIsInZhbHVlIjoiT3J3S3R1ZzFQdUxIaW5aZjFnNUNtd3lsRWM3TEpHdnh0SFNhTUgxei91dnJsMHRlcDRTckE3QjZhRXlvZ3hhZUlnUVBTRC9IdHYzdjJ3OGdJUW1RUzM4RWFLWGpZRlA2eW9icWIrNXRjTC9KVWROUjVQSU5iZGpGWU9nQW00Z2MiLCJtYWMiOiJkMjY3ZjM5NzVhZTJhYmM0MDFiNjRmZDRjYjVjZGVlY2U5NWViYWMxMzVlNTdhNjEwNGMwMDQ1ZDFmNDAwNjFhIiwidGFnIjoiIn0=;" \
-F "name=Han Solo" \
-F "email=han@millennium-falcon.com" \
-F "password=myfalcon" \
-F "password_confirmation=myfalcon" \
http://localhost:8000/register

If we check our database now we should have a new user! So with our new user let's try and login

curl -v -X POST \
-H "Accept: application/json" \
-H "Access-Control-Allow-Credentials: true" \
-H "Referer: localhost:8000" \
-H "X-XSRF-TOKEN: eyJpdiI6IlQxaVhQcUplbmN5M0Z2d1V0ZFV0SGc9PSIsInZhbHVlIjoiN2Z2QnozbGdDV2Jac0tzTk1FaFB6cndNVlVocDlNRjN4bkt5UHZUR3VmaG9YYmszSUlNNmowd0RPbTVPcHFKMHpoU0tyWGRkVWUxUlZkYi9aanlnemxsREJlK2ZreWtqb1pzQ2hVNExLcEJranFiZ1RiVW1VWUpJVVB5am9FVjAiLCJtYWMiOiJjY2RmNDQ1Y2MwZDFkNDQ0Y2JjNTkzZmNmY2I1NjQxOGJkMzU3YWI0MWUxNjJkMjYwY2FlYWM0YmJmYjRhMmIxIiwidGFnIjoiIn0=" \
-H "Cookie: XSRF-TOKEN=eyJpdiI6IlQxaVhQcUplbmN5M0Z2d1V0ZFV0SGc9PSIsInZhbHVlIjoiN2Z2QnozbGdDV2Jac0tzTk1FaFB6cndNVlVocDlNRjN4bkt5UHZUR3VmaG9YYmszSUlNNmowd0RPbTVPcHFKMHpoU0tyWGRkVWUxUlZkYi9aanlnemxsREJlK2ZreWtqb1pzQ2hVNExLcEJranFiZ1RiVW1VWUpJVVB5am9FVjAiLCJtYWMiOiJjY2RmNDQ1Y2MwZDFkNDQ0Y2JjNTkzZmNmY2I1NjQxOGJkMzU3YWI0MWUxNjJkMjYwY2FlYWM0YmJmYjRhMmIxIiwidGFnIjoiIn0=; K8ogV76bi5BblE3XE150Y93owKipa4ju9m3cRxgN=eyJpdiI6InpiWUY1WkkxY0hzQjBrZ3V5Vlo3M1E9PSIsInZhbHVlIjoiVWNCejRhSzBNTm1oS3FRd21yNEcxak1FWVJUMGZFdFRsL25EV0g1aFFQaE1TelVuN0J4S2oyTXVTemJaREdKVC9mV3NrYnRnOWU5Z1NYcC84eVFHUFk5THFnUlQ4QnNvdnBURmwwR2UxUy9tZ3V5YXlWZFBIUXVDY3FKVEIxbjlJM2I4OS84RldzWXg4Wm1YS3E1clpCbm5FM3l6Z2JyeEowVW9RNkpyNDgycC9aSm9IK1I3QnVtQm1tV3MrQWd6OS9DYlBYR2dpcDVkcVRQN0VMRWVUR2t1ZjdWS3Zobk9qVlpWQ1RHR3dwSWZ0Y0RHVFlkSDg2RWx3amJzdlo4dWE0NDFsajBuQm5yYWtvVDhkQXBjVlQ1WVdoYWQ3ZERkSVNBT3diV0xCL1YyMkhNb0JTUGg4b0hVL2pxK3lpanRha1VwS2s0MTdqSUNSQTdMZ2R5NE00aFlqUnQ5M2pUYlJzemdGRG1QSnVQcjQvRXhXM1lnaW9rS0NhQUdoZUtZK0J4RXRmcXhDQWsxTjYrL1BBSzIwdz09IiwibWFjIjoiNjRhMWUyZGJlMjgzYjJhNTA1YjVkOTQwZjRkZDNkNmQ2ZjE0Yjc3YWFjZmYxNjVkOWRhOWU3ZTUwZDM5NWZmOCIsInRhZyI6IiJ9; laravel_session=eyJpdiI6ImY3YXl2Q3hSNElNMFZ0K2szNlFxWXc9PSIsInZhbHVlIjoiem03OXNZbFdTMk10a0tkZk9zcUZhMjdUeWZqOHRJNjdPMnlIMXpXV214ZXlwZENlRWc2d0VibGYrUlpNTDRtVXVNQVhwUkhMeHhkaTFhUkJqb2JKOFlVODZkK1dvWXJIckEyRnhsanJLbHBvRXE5a1VJV1ZyMnlLVFJuRE5wWUMiLCJtYWMiOiI3N2VkZGY2NjdiMzAxMTJhZjQxOWViZjYxNGY5NjZkNmEyYzI5ZThlZjVhMDM2MWJlMWY0MzZlMWE3ZDY4NDlmIiwidGFnIjoiIn0=;" \
-F "email=han@millennium-falcon.com" \
-F "password=myfalcon" \
http://localhost:8000/login

Should get the response {"two_factor":false} and we should now be logged in. Let's try and fetch the user information. Remember that the Cookies have changed so we need to use the new cookies we got back. Recommend using e.g. Postman for testing the API calls so you do not need to include the cookie header all the time.

curl -v \
-H "Accept: application/json" \
-H "Access-Control-Allow-Credentials: true" \
-H "Referer: localhost:8000" \
-H "X-XSRF-TOKEN: eyJpdiI6IlZudytBbDVUWTFCa0JCM3BCNjQxVGc9PSIsInZhbHVlIjoiUjJxZ2c2T3c4SnROVjJZY2hiOGZIZDJhWEkyT3Vzc0F5TWM5RUlHS3dROWpZZnhMTmI1YXA2enQ1dE4ycFRhTEt1SmljbDdiZU91UXNPSHJZNFR6SlBTaGpSekZqcVMwSHIyK2w3SXA1dUhVOHV1bEtGT0ZnMDcxbzA4bzRvdHYiLCJtYWMiOiJkNzkyNDgwYjk0YzFjMTA1NTc0ZWZiYWJiMWMxZDBlMmQzYmVmYmQzNWNjZTlkOTllNzgwYzVmMDJiMGFhY2M1IiwidGFnIjoiIn0=" \
-H "Cookie: XSRF-TOKEN=eyJpdiI6IlZudytBbDVUWTFCa0JCM3BCNjQxVGc9PSIsInZhbHVlIjoiUjJxZ2c2T3c4SnROVjJZY2hiOGZIZDJhWEkyT3Vzc0F5TWM5RUlHS3dROWpZZnhMTmI1YXA2enQ1dE4ycFRhTEt1SmljbDdiZU91UXNPSHJZNFR6SlBTaGpSekZqcVMwSHIyK2w3SXA1dUhVOHV1bEtGT0ZnMDcxbzA4bzRvdHYiLCJtYWMiOiJkNzkyNDgwYjk0YzFjMTA1NTc0ZWZiYWJiMWMxZDBlMmQzYmVmYmQzNWNjZTlkOTllNzgwYzVmMDJiMGFhY2M1IiwidGFnIjoiIn0=; zGvmvStSlrsqxdEOfTx7gcTRL4bIFpq1kcDB5W5b=eyJpdiI6ImRxazFzNkVWNW1yK1crblNoRjRONlE9PSIsInZhbHVlIjoiQzJoTHZWUVNCakpMaVRGOG1ibFFIQW10dWRjKzFBRmdrMUdLQU9QeUhTUkk3a0lzUTc5ZmF5TU00M3RGc0lQckxsMlY4QVEvRU91RzYvUGVQV2pFMUJ2bW55OFE1aUVrUVE3ZTZZVm5Ed2h3YTNIZUZzamJlRUh4QjUwZStBTXRmT3pFSHlETmNUMVdpZis1ckFGWGRyY0pFR1NLUXJrWVpJdVZNRjE2NEY3MVlacGt5bmU4cVVvczYxQVAxOUpKWWxuZiszTk53LzQ1V1lQdDlsSW5Ib2c3c3NkNGxJUU1lZUszU1dpSTd2MVdlcm13WFR5cE5QUjVwU2NHeHFrbUkydW9QWEN3VXo3SVNXemdaVzhSWTZrUHFOdzVDbGlMY01Zc2loZHgvbHBuVUZVUGJjUnJPc0VSTUU3VFk1Z1I1VjIvaTNIT1B2Rk9QNnpSVVJvdTlmVGZLaitlUFNxa0lMMS8xbUMyaFBLWXNsOTIwSGRqbUV4OGNMdGo4bGpoL3l5T2JwaDNwL3lteFpFTjV5YU9sZHhnU3RIU01mcmZmWHVlR1VDbnVoMWI2V0lVSUZIRjh6N0YxbUdub0R0YTRMWHNYWHhnVzNEL0pudGpnUkdnamFSTTExcG1KYS92VW5CSWZlSXhZUW89IiwibWFjIjoiYjM3MDNkYzMwZDI3ZTM2ZjY4OTk2Mjg1NDNlZmE1YTY0MWFlMWY1YWI0MTA3OWRiYTU1MDcxOTVlYTQ0NGIwYiIsInRhZyI6IiJ9; laravel_session=eyJpdiI6IjZ6aDBmS1VDdmxoWVU1aWdLZGRsSlE9PSIsInZhbHVlIjoiTHJsUUhGRWlhOHNiejNIckV2TFl4RWFXaDBUS0l2NGlaVXd2MUxPb2htTjhjM1l2NUJaNkg2NE1wYkc4a2k0Sk1ab2ZYSWZzdExZM1cvaWRpRHZQYlM1QjFEWEpXcGlpOVdMYzJSS1FoejFPdTNNeXU4eDdJb0N4R1M4cHNEMEwiLCJtYWMiOiI0YzlmOTRhYzIyMGQ5Y2VjZDhkNjI0M2NhMTE3OTdkYmUwOGEyYzhkN2UyNDhmYTQ2NWExYzYxMzM5NDNjYjdlIiwidGFnIjoiIn0=;" \
http://localhost:8000/user

We should now get the user data back!

{
  "id":10,
  "name":"Han Solo",
  "email":"han@millennium-falcon.com",
  "email_verified_at":null,
  "two_factor_secret":null,
  "two_factor_recovery_codes":null,
  "created_at":"2022-01-30T17:20:44.000000Z",
  "updated_at":"2022-01-30T17:20:44.000000Z"
}

Now we have everything up and running to be able to include a frontend application.