> For the complete documentation index, see [llms.txt](/docs/llms.txt)

# Laravel | FusionAuth Docs

Quickstart integration of PHP Laravel web application with FusionAuth.

# Laravel

In this quickstart, you are going to build an application with Laravel and integrate it with FusionAuth. You'll be building it for [ChangeBank](https://www.youtube.com/watch?v=CXDxNCzUspM), a global leader in converting dollars into coins. It'll have areas reserved for users who have logged in as well as public facing sections.

The Docker Compose file and source code for a complete application are available at [https://github.com/FusionAuth/fusionauth-quickstart-php-laravel-web](https://github.com/FusionAuth/fusionauth-quickstart-php-laravel-web).

## Prerequisites[#](#prerequisites)

For this Quickstart, you'll need:

*   [PHP](https://www.php.net/manual/en/install.php) 8.1
*   [Composer](https://getcomposer.org/)
*   [Node.js](https://nodejs.org/)
*   [Docker](https://www.docker.com), version 20 or later, which is the quickest way to start FusionAuth. (There are [other ways](/docs/get-started/download-and-install)).

## General Architecture[#](#general-architecture)

Here's a typical application login flow before FusionAuth:

```mermaid
sequenceDiagram
    participant User
    participant App as Application

    User ->> App : View Homepage
    User ->> App : Click Login Link
    App ->> User : Show Login Form
    User ->> App : Fill Out and Submit Login Form
    App ->> App : Authenticates User
    App ->> User : Display User's Account or Other Info
```

Request flow during login before FusionAuth

And here's the same application login flow after introducing FusionAuth:

```mermaid
sequenceDiagram
    participant User
    participant App as Application
    participant FusionAuth

    User ->> App : View Homepage
    User ->> App : Click Login Link (to FusionAuth)
    User ->> FusionAuth : View Login Form
    FusionAuth ->> User : Show Login Form
    User ->> FusionAuth : Fill Out and Submit Login Form
    FusionAuth ->> FusionAuth : Authenticates User
    FusionAuth ->> User: Go to Redirect URI
    User ->> App: Request the Redirect URI
    App ->> FusionAuth : Is User Authenticated?
    FusionAuth ->> App : User is Authenticated
    App ->> User : Display User's Account or Other Info
```

Request flow during login after FusionAuth

In general, you are introducing FusionAuth in order to normalize and consolidate user data. This helps make sure it is consistent and up-to-date as well as offloading your login security and functionality to FusionAuth.

## Getting Started[#](#getting-started)

Start with getting FusionAuth up and running and creating a new Laravel application.

### Clone The Code[#](#clone-the-code)

First, grab the code from the repository and change into that folder.

```
git clone https://github.com/FusionAuth/fusionauth-quickstart-php-laravel-web.git
cd fusionauth-quickstart-php-laravel-web
```

All shell commands in this guide can be entered in a terminal in this folder. On Windows, you need to replace forward slashes with backslashes in paths.

All the files you'll create in this guide already exist in the `complete-application` subfolder, if you prefer to copy them.

### Run FusionAuth Via Docker[#](#run-fusionauth-via-docker)

You'll find a Docker Compose file (`docker-compose.yml`) and an environment variables configuration file (`.env`) in the root directory of the repo.

Assuming you have Docker installed, you can stand up FusionAuth on your machine with the following.

```
docker compose up -d
```

Here you are using a bootstrapping feature of FusionAuth called [Kickstart](/docs/get-started/download-and-install/development/kickstart). When FusionAuth comes up for the first time, it will look at the `kickstart/kickstart.json` file and configure FusionAuth to your specified state.

If you ever want to reset the FusionAuth application, you need to delete the volumes created by Docker Compose by executing `docker compose down -v`, then re-run `docker compose up -d`.

FusionAuth will be initially configured with these settings:

*   Your client Id is `E9FDB985-9173-4E01-9D73-AC2D60D1DC8E`.
*   Your client secret is `super-secret-secret-that-should-be-regenerated-for-production`.
*   Your example username is `richard@example.com` and the password is `password`.
*   Your admin username is `admin@example.com` and the password is `password`.
*   The base URL of FusionAuth is `http://localhost:9011/`.

You can log in to the [FusionAuth admin UI](http://localhost:9011/admin) and look around if you want to, but with Docker and Kickstart, everything will already be configured correctly.

If you want to see where the FusionAuth values came from, they can be found in the [FusionAuth app](http://localhost:9011/admin). The tenant Id is found on the Tenants page. To see the Client Id and Client Secret, go to the Applications page and click the `View` icon under the actions for the ChangeBank application. You'll find the Client Id and Client Secret values in the `OAuth configuration` section.

The `.env` file contains passwords. In a real application, always add this file to your `.gitignore` file and never commit secrets to version control.

### The Basic Laravel Application[#](#the-basic-laravel-application)

While this guide builds a new Laravel project, you can use the same method to integrate your existing project with FusionAuth.

To create a new Laravel project run the following command. There are other ways of creating the project as suggested in the Laravel [documentation](https://laravel.com/docs/10.x#your-first-laravel-project).

```
composer create-project laravel/laravel your-application && cd your-application
```

Composer will initialize the project and add all the starter app files in `your-application` directory.

If you are not familiar with Laravel, here's a brief explanation of its structure:

*   `app` — contains the lowest level code: models, controllers, and middleware.
*   `bootstrap` — the starting point for execution.
*   `config` — contains all settings.
*   `database` — contains all database migration scripts.
*   `public` — contains static resources (images, CSS) available to anyone on the web.
*   `resources` — similar to `public`, but contains Blade templates, and files are adapted by the server before serving.
*   `routes` — contains the server code to respond to URLs called by a browser.
*   `storage` — cache folder managed by Laravel when running.
*   `tests` — where you write your tests.
*   `vendor` — contains all Composer packages.

## Authentication[#](#authentication)

Authentication in Laravel is managed by [Socialite](https://laravel.com/docs/10.x/socialite). For this application, you need the [FusionAuth Socialite provider](https://github.com/SocialiteProviders/FusionAuth). To install it, run the following command.

```
composer require socialiteproviders/fusionauth
```

In `your-application/.env`, insert the following lines.

```
FUSIONAUTH_CLIENT_ID="E9FDB985-9173-4E01-9D73-AC2D60D1DC8E"
FUSIONAUTH_CLIENT_SECRET="super-secret-secret-that-should-be-regenerated-for-production"
FUSIONAUTH_BASE_URL="http://localhost:9011"
FUSIONAUTH_REDIRECT_URL="http://localhost:8000/auth/callback"
# FUSIONAUTH_TENANT_ID=
```

This tells Laravel where to find and connect to FusionAuth.

This quickstart will use a [SQLite](https://www.sqlite.org/index.html) database. SQLite is a small, fast, self-contained database engine, but you can use MySQL or Postgres if you have them installed on your machine.

Create a SQLite database by running the following command from `your-application` directory.

```
touch database/database.sqlite
```

In the `your-application/.env` file, comment out the values for the following keys as shown here.

```
# DB_CONNECTION=mysql
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
```

Add the following key above the keys you just commented out.

```
DB_CONNECTION=sqlite
```

This enables Laravel to connect to the SQLite database.

In the file `your-application/config/services.php`, add one more item to the array being returned with the code below.

```
'fusionauth' => [
    'client_id' => env('FUSIONAUTH_CLIENT_ID'),
    'client_secret' => env('FUSIONAUTH_CLIENT_SECRET'),
    'base_url' => env('FUSIONAUTH_BASE_URL'),
    'redirect' => env('FUSIONAUTH_REDIRECT_URL'),
    'tenant_id' => env('FUSIONAUTH_TENANT_ID'),
],
```

Now the configuration files have all the settings you need. Next, you'll change your provider to be aware of FusionAuth.

In the file `your-application/app/Providers/EventServiceProvider.php`, replace the `$listen` array with the code below.

```
protected $listen = [
    Registered::class => [
        SendEmailVerificationNotification::class,
    ],
    \SocialiteProviders\Manager\SocialiteWasCalled::class => [
        \SocialiteProviders\FusionAuth\FusionAuthExtendSocialite::class . '@handle',
    ],
];
```

Finally, update your User model to have FusionAuth fields.

In the file `your-application/app/Models/User.php`, add the FusionAuth items to the `$fillable` and `$hidden` properties with the code below.

```
/**
 * The attributes that are mass assignable.
 *
 * @var array<int, string>
 */
protected $fillable = [
    'name',
    'email',
    'password',
    'fusionauth_id',
    'fusionauth_access_token',
    'fusionauth_refresh_token',
];

/**
 * The attributes that should be hidden for serialization.
 *
 * @var array<int, string>
 */
protected $hidden = [
    'password',
    'remember_token',
    'fusionauth_id',
    'fusionauth_access_token',
    'fusionauth_refresh_token',
];
```

Every model in Laravel uses the database, so you need to update the database table to match your new model.

Create a file named `your-application/database/migrations/2023_11_03_112258_add_fusionauth_fields_user_table.php` or run the command below to create it.

Running the command below will create the file with a slightly different name depending on the date you are running the command.

```
php artisan make:migration add_fusionauth_fields_user_table
```

The result is the same whether you create the database scripts manually or with this command, so you can use your preferred method.

Add the code below to the file.

```
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('users',  function (Blueprint $table) {
            $table->string('fusionauth_id', 36)->unique();
            $table->text('fusionauth_access_token');
            $table->text('fusionauth_refresh_token')->nullable();
            $table->string('password')->nullable()->change();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('users',  function (Blueprint $table) {
            $table->dropColumn('fusionauth_id');
            $table->dropColumn('fusionauth_access_token');
            $table->dropColumn('fusionauth_refresh_token');
            $table->string('password')->nullable(false)->change();
        });
    }
};
```

Install the following package required to allow manipulation of columns.

```
composer require doctrine/dbal
```

Run the command below to update your database with the new User definition.

```
php artisan migrate
```

## Customization[#](#customization)

Now that authentication is done, the last task is to create example pages that a user can access only when logged in.

### CSS And HTML[#](#css-and-html)

Copy over some CSS and images from the example app.

```
cp ../complete-application/public/changebank.css public/changebank.css
cp ../complete-application/public/money.jpg public/money.jpg
cp ../complete-application/public/changebank.svg public/changebank.svg
```

Next, create an `index.blade.php` file.

```
touch resources/views/index.blade.php
```

Paste the following code into the file.

```
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>FusionAuth OpenID and PKCE example</title>
    <link rel="stylesheet" href="changebank.css" />
</head>
<body>
<div id="page-container">
    <div id="page-header">
        <div id="logo-header">
            <img
                src="changebank.svg"
            />
            <a class="button-lg" href="/login">Login</a>
        </div>

        <div id="menu-bar" class="menu-bar">
            <a class="menu-link">About</a>
            <a class="menu-link">Services</a>
            <a class="menu-link">Products</a>
            <a class="menu-link" style="text-decoration-line: underline">Home</a>
        </div>
    </div>

    <div style="flex: 1">
        <div class="column-container">
            <div class="content-container">
                <div style="margin-bottom: 100px">
                    <h1>Welcome to Changebank</h1>
                    <p>
                        To get started,
                        <a href="/login">log in or create a new account</a>.
                    </p>
                </div>
            </div>
            <div style="flex: 0">
                <img src="money.jpg" style="max-width: 800px" />
            </div>
        </div>
    </div>
</div>
</body>
</html>
```

The index page contains nothing to note except a link to the login page `<a href="/login">`.

Next, create an `account.blade.php` file.

```
touch resources/views/account.blade.php
```

Paste the following code into the file.

```
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>FusionAuth OpenID and PKCE example</title>
    <link rel="stylesheet" href="changebank.css" />
</head>
<body>
<div id="page-container">
    <div id="page-header">
        <div id="logo-header">
            <img src="changebank.svg" />
            <div class="h-row">
                <p class="header-email">{{$email}}</p>
                <a class="button-lg" href="/logout"> Logout </a>
            </div>
        </div>

        <div id="menu-bar" class="menu-bar">
            <a class="menu-link inactive" href="/change">Make Change</a>
            <a class="menu-link" href="/account">Account</a>
        </div>
    </div>

    <div style="flex: 1;">
        <!-- Application page -->
        <div class="column-container">
            <div class="app-container">
                <h3>Your balance</h3>
                <div class="balance">$0.00</div>
            </div>
        </div>
    </div>
</div>
</body>
</html>
```

The account page displays the user's email from FusionAuth, as you'll see later when you create the route `<p class="header-email">{{$email}}</p>`.

Next, create a `change.blade.php` file.

```
touch resources/views/change.blade.php
```

Paste the following code into the file.

```
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>FusionAuth OpenID and PKCE example</title>
    <link rel="stylesheet" href="changebank.css" />
</head>

<body>
<div id="page-container">
    <div id="page-header">
        <div id="logo-header">
            <img src="changebank.svg" />
            <div class="h-row">
                <p class="header-email">{{$email}}</p>
                <a class="button-lg" href="/logout"> Logout </a>
            </div>
        </div>

        <div id="menu-bar" class="menu-bar">
            <a class="menu-link" href="/change">Make Change</a>
            <a class="menu-link inactive" href="/account">Account</a>
        </div>
    </div>

    <div style="flex: 1;">
        <div class="column-container">
            <div class="app-container change-container">
                <h3>We Make Change</h3>

                @if ($state['error'] && $state['hasChange'])
                    <div class="error-message">Please enter a dollar amount</div>
                @endif

                @if (!$state['hasChange'])
                    <div class="error-message"></div>
                @endif

                @if (!$state['error'] && $state['hasChange'])
                    <div class="change-message">
                        We can make change for {{ $state['total'] }} with {{ $state['nickels'] }} nickels and {{ $state['pennies'] }} pennies!
                    </div>
                @endif

                <form method="post" action="/change">
                    @csrf
                    <div class="h-row">
                        <div class="change-label">Amount in USD: $</div>
                        <input class="change-input" name="amount" value="0.00" />
                        <input class="change-submit" type="submit" value="Make Change" />
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>
</body>

</html>
```

The change page uses some Blade conditional statements to display different `div`s depending on the state returned by the form POST `@if ($state['error'] && $state['hasChange'])`.

The change page also includes a `@csrf` anti-forgery token in the form at the bottom to prevent Laravel throwing page expired errors.

### Route[#](#route)

In the file `your-application/routes/web.php`, overwrite everything with the code below.

```
<?php

use Illuminate\Support\Facades\Route;
use Illuminate\Http\Request;
use Laravel\Socialite\Facades\Socialite;
use App\Models\User;
use Illuminate\Support\Facades\Auth;


Route::get('/', function () {
    if (auth()->check())
        return redirect('/account');
    return view('index');
});


Route::get('/login', function () {
     return Socialite::driver('fusionauth')->redirect();
})->name('login');


Route::get('/auth/callback', function () {
    /** @var \SocialiteProviders\Manager\OAuth2\User $user */
    $user = Socialite::driver('fusionauth')->user();
    $user = User::updateOrCreate([
        'fusionauth_id' => $user->id,
    ], [
        'name' => $user->name,
        'email' => $user->email,
        'fusionauth_access_token' => $user->token,
        'fusionauth_refresh_token' => $user->refreshToken,
    ]);
    Auth::login($user);
    return redirect('/account');
});


Route::get('/logout', function () {
    Auth::logout();
    return redirect('/');
});


Route::get('/account', function () {
    return view('account', ['email' => Auth::user()->email]);
})->middleware('auth');


Route::get('/change', function () {
    $state = [
        'error' => false,
        'hasChange' => false,
        'total' => '',
        'nickels' => '',
        'pennies' => '',
    ];
    return view('change', ['state' => $state, 'email' => Auth::user()->email]);
})->middleware('auth');


Route::post('/change', function (Request $request) {
    $amount = $request->input('amount');
    $state = [
        'error' => false,
        'hasChange' => true,
        'total' => '',
        'nickels' => '',
        'pennies' => '',
    ];

    $total = floor(floatval($amount) * 100) / 100;
    $state['total'] = is_nan($total) ? '' : number_format($total, 2);

    $nickels = floor($total / 0.05);
    $state['nickels'] = number_format($nickels);

    $pennies = ($total - (0.05 * $nickels)) / 0.01;
    $state['pennies'] = ceil(floor($pennies * 100) / 100);

    $state['error'] = !preg_match('/^(\d+(\.\d*)?|\.\d+)$/', $amount);

    return view('change', ['state' => $state, 'email' => Auth::user()->email]);
})->middleware('auth');
```

The homepage route `/` checks whether you are logged in and redirects you to your account page if you are.

After login, FusionAuth redirects you to the `/auth/callback` route. Use Socialite to update the user model in the database with `$user = User::updateOrCreate([`, then start a session for the user with `Auth::login($user);`.

Logout ends the session with `Auth::logout();`.

The account and change routes both include the user's email in the page template data, `return view('account', ['email' => Auth::user()->email]);`. Both routes use middleware to prevent users without a session from seeing the page, `})->middleware('auth');`.

Finally, the change route has both a GET and a POST version. Both versions return the same view, but the POST route does a calculation and includes more template data.

## Run The Application[#](#run-the-application)

From the `your-application` directory, run the following command.

```
php artisan serve
```

Browse to [the app](http://localhost:8000) at [http://localhost:8000](http://localhost:8000). Log in using `richard@example.com` and `password`. The change page will allow you to enter a number. Log out and verify that you can't browse to the [account page](http://localhost:8000/account).

## Made it this far? Want a free t-shirt? We got ya.

Thank you for spending some time getting familiar with FusionAuth.

\*Offer only valid in the United States and Canada, while supplies last.

## Next Steps[#](#next-steps)

This quickstart is a great way to get a proof of concept up and running quickly, but to run your application in production, there are some things you're going to want to do.

### FusionAuth Customization

FusionAuth gives you the ability to customize just about everything to do with the user's experience and the integration of your application. This includes:

*   [Hosted pages](/docs/customize/look-and-feel/) such as login, registration, email verification, and many more.
*   [Email templates](/docs/customize/email-and-messages/email-templates).
*   [User data and custom claims in access token JWTs](/articles/tokens/jwt-components-explained).

### Security

*   You may want to customize the [token expiration times and policies](/docs/lifecycle/authenticate-users/oauth/) in FusionAuth.
*   Choose [password rules](/docs/get-started/core-concepts/tenants#password) and a [hashing algorithm](/docs/reference/password-hashes) that meet your security needs.

### Tenant and Application Management

*   Model your application topology using [Applications](/docs/get-started/core-concepts/applications), [Roles](/docs/get-started/core-concepts/roles), [Groups](/docs/get-started/core-concepts/groups), [Entities](/docs/get-started/core-concepts/groups), and more.
*   Set up [MFA](/docs/lifecycle/authenticate-users/multi-factor-authentication), [Social login](/docs/lifecycle/authenticate-users/identity-providers/), or [SAML](/docs/lifecycle/authenticate-users/identity-providers/overview-samlv2) integrations.
*   Integrate with external systems using [Webhooks](/docs/extend/events-and-webhooks/), [SCIM](/docs/lifecycle/migrate-users/scim), and [Lambdas](/docs/extend/code/lambdas/).

### Laravel Authentication[#](#laravel-authentication)

*   [Laravel Socialite concepts](https://laravel.com/docs/10.x/socialite)

## Troubleshooting[#](#troubleshooting)

*   I get "This site can't be reached localhost refused to connect" when I click the Login button.

Ensure FusionAuth is running in the Docker container. You should be able to log in as the admin user `admin@example.com` with a password of `password` at [http://localhost:9011/admin](http://localhost:9011/admin).

*   Laravel says there is an invalid state exception.

Browse to the homepage, log out, and try to log in again. If that still doesn't work, delete and restart all the containers.

*   It still doesn't work.

You can always pull down a complete running application and compare what's different.

```
git clone https://github.com/FusionAuth/fusionauth-quickstart-php-laravel-web.git
cd fusionauth-quickstart-php-laravel-web
docker compose up -d
cd complete-application
composer install
touch database/database.sqlite
php artisan migrate
php artisan serve
```

Browse to the app at [http://localhost:8000](http://localhost:8000).