EthicsComputerCourses
Join training +91 90532 10052

Authentication with Starter Kits

Laravel · Lesson 16 of 30 Intermediate 8 min read Updated

By the end of this lesson you will know how to add a complete login system to a Laravel app in minutes with a starter kit, and how login works underneath by building a small manual version for the coaching office. You will also learn how to protect pages, read the current user and stop password-guessing attacks.

Authentication in plain words

Authentication answers "who are you?". Authorisation, the next lesson, answers "what are you allowed to do?". At a school gate, showing your ID card is authentication. Being allowed into the staff room is authorisation.

Laravel stores users in the users table (created by the very first migration) and uses the App\Models\User model. After a successful login, it remembers the user's ID in the session, so every later request knows who is visiting.

A starter kit is a complete, ready-made app skeleton with login, registration, password reset, email verification, two-factor authentication and a settings page. The code is copied into your project, so you own it and can change anything.

Laravel 13 offers four official starter kits:

Starter kitFront endBuilt with
ReactReact 19 pages through InertiaTypeScript, Tailwind, shadcn/ui
VueVue 3 Composition API through InertiaTypeScript, Tailwind, shadcn-vue
SvelteSvelte 5 through InertiaTypeScript, Tailwind, shadcn-svelte
LivewireBlade and Livewire 4Tailwind, Flux UI

All four use Laravel Fortify, a back-end package that provides the login logic and routes. Each kit also has a WorkOS AuthKit variant that adds social login (Google, Microsoft, GitHub, Apple), passkeys and single sign-on through an external account.

Step 1. Create the app with the installer and choose a kit when asked:

Terminal
laravel new coaching-portal

Step 2. Install front-end packages and start everything:

Terminal
cd coaching-portal
npm install && npm run build
composer run dev

Step 3. Open http://localhost:8000/register, create an account and you are logged in. Fortify registers routes like these, depending on the features switched on in config/fortify.php:

RouteMethodPurpose
/loginGET, POSTShow the form, log in
/logoutPOSTLog out
/registerGET, POSTSign-up form, create the user
/forgot-passwordGET, POSTAsk for a reset link
/reset-password/{token}GETReset form from the email link
/email/verifyGET"Please verify your email" notice
/two-factor-challengeGET, POSTAsk for the 2FA code

To turn a feature off, remove it from the features array in config/fortify.php. For a coaching office where only the admin creates staff accounts, remove Features::registration() so nobody can sign up from outside.

To change what happens at registration, edit the action classes in app/Actions/Fortify, such as CreateNewUser.php.

Option 2: how login works, by hand

Starter kits hide the details. Writing a tiny login yourself shows what is really happening. This is the coaching office's staff login.

Step 1. The controller:

PHP
<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    public function store(Request $request): RedirectResponse
    {
        $credentials = $request->validate([
            'email' => ['required', 'email'],
            'password' => ['required'],
        ]);

        if (! Auth::attempt($credentials, $request->boolean('remember'))) {
            return back()
                ->withErrors(['email' => 'These details do not match our records.'])
                ->onlyInput('email');
        }

        $request->session()->regenerate();

        return redirect()->intended('/admin/dashboard');
    }

    public function destroy(Request $request): RedirectResponse
    {
        Auth::logout();
        $request->session()->invalidate();
        $request->session()->regenerateToken();

        return redirect('/');
    }
}

What each piece does:

  1. Auth::attempt() finds the user by email and checks the password against the stored hash with password_verify(). It returns true and logs the user in, or false.
  2. The second argument turns on remember me, a long-lived cookie that keeps the user logged in after the session ends.
  3. session()->regenerate() gives the browser a new session ID after login, to block session fixation.
  4. redirect()->intended() sends the user to the page they originally wanted, or to the default.
  5. Logout clears the user, destroys the session and makes a new CSRF token.

Step 2. The routes, with middleware:

PHP
use App\Http\Controllers\LoginController;

Route::get('/login', [LoginController::class, 'create'])->middleware('guest')->name('login');
Route::post('/login', [LoginController::class, 'store'])->middleware(['guest', 'throttle:5,1']);
Route::post('/logout', [LoginController::class, 'destroy'])->middleware('auth')->name('logout');

Route::prefix('admin')->middleware('auth')->group(function () {
    Route::get('/dashboard', fn () => 'Admin dashboard. Welcome, '.auth()->user()->name);
});

The login route is named login, because the auth middleware redirects guests to the route with that name. throttle:5,1 allows only 5 login attempts per minute from one visitor, which slows down password guessing.

Step 3. Test the whole flow. These are the real responses from curl with a cookie jar, using the seeded admin user:

Output
GET  /admin/dashboard (not logged in)     -> 302 to /login
POST /login with a wrong password          -> 302 to /login
     form shows: These details do not match our records.
     email box still contains admin@example.com
POST /login with the right password        -> 302 to /admin/dashboard
GET  /admin/dashboard                      -> Admin dashboard. Welcome, Office Admin
GET  /login (already logged in)            -> 302 to /dashboard

The last line shows the guest middleware in action: logged-in users are sent away from the login page. Notice the error message does not say whether the email or the password was wrong; that would help an attacker find valid emails.

Working with the logged-in user

PHP
use Illuminate\Support\Facades\Auth;

$user = Auth::user();          // the User model, or null for guests
$id = Auth::id();              // just the ID
$isIn = Auth::check();         // true or false

$user = auth()->user();        // helper version
$user = $request->user();      // inside a controller with Request

In Blade, show parts of a page only to logged-in visitors or only to guests:

HTML
@auth
    <p>Logged in as {{ auth()->user()->name }}</p>
    <form method="POST" action="{{ route('logout') }}">
        @csrf
        <button>Log out</button>
    </form>
@endauth

@guest
    <a href="{{ route('login') }}">Staff login</a>
@endguest

Passwords

Never store plain passwords. The User model casts password as 'hashed', so User::create(['password' => 'secret123']) stores a bcrypt hash automatically. To hash or check by hand, use the Hash facade: Hash::make($plain) and Hash::check($plain, $hash). The BCRYPT_ROUNDS setting in .env controls how slow (and therefore how hard to crack) hashing is.

Common mistakes

  • Not naming the login route login. The auth middleware cannot find where to send guests and throws an error.
  • Forgetting session()->regenerate() in a custom login. It leaves the app open to session fixation.
  • Leaving public registration on. For internal office apps, disable Features::registration() and create staff accounts yourself.
  • Writing "email not found" or "wrong password". Use one general message for both.
  • No rate limit. Without throttle, bots can try thousands of passwords.

Key points

  • Laravel 13 starter kits (React, Vue, Svelte, Livewire) give full authentication through Fortify; the WorkOS variants add social login and passkeys.
  • Fortify features such as registration and 2FA are switched on or off in config/fortify.php.
  • Auth::attempt() checks hashed passwords and logs in; then regenerate the session and redirect with intended().
  • Protect routes with the auth middleware, keep logged-in users off login pages with guest, and slow attackers with throttle.
  • Read the user with Auth::user(), auth()->user() or $request->user(); use @auth and @guest in Blade.

Exercise

Extend the manual login so that only active staff can log in. Add a boolean is_active column to users (default true). Change the Auth::attempt() call so that inactive users get the same "These details do not match our records." error. Then show a "Log out" button in your layout only for logged-in users.

Show solution
PHP
// migration
Schema::table('users', function (Blueprint $table) {
    $table->boolean('is_active')->default(true);
});
PHP
// LoginController::store()
if (! Auth::attempt([...$credentials, 'is_active' => true], $request->boolean('remember'))) {
    return back()
        ->withErrors(['email' => 'These details do not match our records.'])
        ->onlyInput('email');
}
HTML
@auth
    <form method="POST" action="{{ route('logout') }}">
        @csrf
        <button>Log out</button>
    </form>
@endauth

Extra keys in the array passed to Auth::attempt() (other than the password) become extra where conditions, so an inactive user simply does not match.

Quick Quiz

1. Which package provides the login logic used by Laravel 13's starter kits?

2. What should you call right after a successful custom login?

3. Where does the auth middleware send a guest by default?

4. What does throttle:5,1 on the login route do?

Learn Laravel with a mentor

Free lessons get you started. Our PHP & MySQL Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call