How to Add Auth to a Laravel App (Breeze, Fortify, Sanctum)
A practical guide to adding authentication to a Laravel app: Breeze, Fortify, Jetstream, and Sanctum compared — with code, trade-offs, and what to pick.
Laravel makes a lot of things easy, and then you go to add auth and find four official packages — Breeze, Fortify, Jetstream, Sanctum — that all sound like they do the same thing. They don’t. Picking the wrong one means either ripping out scaffolding you didn’t want or rebuilding features you should have gotten for free.
This guide cuts through it. We’ll cover when to add auth to a Laravel app with each official package, what the code actually looks like, and the trade-offs that matter when you’re a solo dev trying to ship.
How Laravel Auth Actually Layers
The reason these packages confuse people is that they sit at different levels of abstraction:
- Fortify is the engine. It’s a headless, backend-only authentication implementation — login, registration, password reset, email verification, two-factor — with no views. You bring your own frontend.
- Breeze publishes plain controllers and views straight into your app. It uses Laravel’s core auth services directly. Minimal, readable, easy to edit.
- Jetstream is a full starter kit built on top of Fortify and Sanctum, adding two-factor, team management, browser session tracking, profile photos, and API tokens.
- Sanctum is for API and SPA token authentication — not a starter kit, a token system.
Once you see the layers, the choice gets simple. Most indie projects want Breeze. Headless apps and mobile backends want Fortify. Apps that genuinely need teams and 2FA on day one want Jetstream.
Option 1: Breeze (the default you probably want)
Breeze is the right call for most projects. It’s clean, gets out of your way, and you own every line because it copies controllers and views into your codebase.
composer require laravel/breeze --dev
php artisan breeze:install
php artisan migrate
npm install && npm run dev
During breeze:install you choose a stack — Blade, Livewire, or an Inertia stack with React or Vue. That gives you working login, registration, password reset, and email verification scaffolding wired to App\Models\User.
Protecting routes is the standard middleware:
// routes/web.php
Route::middleware(['auth'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
});
And in a controller you get the user from the request:
public function index(Request $request)
{
$user = $request->user(); // the authenticated User model
return view('dashboard', ['user' => $user]);
}
Because Breeze publishes real controllers, customizing the login flow is just editing PHP you can read — no digging through vendor internals.
Best for: CRUD apps, admin panels, internal tools, most MVPs. If you’re unsure, start here.
Option 2: Fortify (backend-only, bring your own frontend)
Fortify gives you all the auth logic with zero views. Reach for it when you have a custom frontend — a React SPA, a separate Vue app, or a mobile client — and you don’t want Laravel rendering forms.
composer require laravel/fortify
php artisan fortify:install
php artisan migrate
Fortify registers the routes (/login, /register, /forgot-password, etc.) and exposes “actions” you customize. For example, controlling what fields a new user gets:
// app/Actions/Fortify/CreateNewUser.php
public function create(array $input): User
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'unique:users'],
'password' => ['required', Password::defaults(), 'confirmed'],
])->validate();
return User::create([
'name' => $input['name'],
'email' => $input['email'],
'password' => Hash::make($input['password']),
]);
}
You render your own login page and POST to Fortify’s routes. More wiring than Breeze, but full control over the UI.
Best for: Custom frontends, mobile backends, anyone who wants the auth logic without Laravel’s views.
Option 3: Sanctum (APIs and SPAs)
Sanctum is a different problem entirely: authenticating API requests. It handles two cases — issuing personal access tokens for API clients, and cookie-based session auth for a first-party SPA on the same domain.
Token issuance is short:
$token = $user->createToken('mobile-app')->plainTextToken;
// client sends: Authorization: Bearer <token>
Then protect API routes:
// routes/api.php
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
You can combine Breeze (web) with Sanctum (API) for a hybrid app that serves both a web UI and a mobile client off the same user table. That’s a common and well-supported pattern.
Best for: API authentication, SPAs, mobile apps talking to a Laravel backend.
Option 4: Jetstream (when you actually need the extras)
Jetstream is Breeze plus enterprise-flavored features: two-factor auth, team/organization management, session management, and API token scoping. It’s built on Fortify and Sanctum.
composer require laravel/jetstream
php artisan jetstream:install livewire # or "inertia"
php artisan migrate
The catch: Jetstream ships a lot of opinions and a lot of generated code. If you don’t need teams or 2FA on day one, you’re maintaining scaffolding you aren’t using. Adopt it when the requirements are real, not preemptively.
Best for: Apps that genuinely need teams and 2FA from the start.
Quick Comparison
| Package | What it is | Views | Best for |
|---|---|---|---|
| Breeze | Published controllers + views | Yes | Most MVPs |
| Fortify | Headless auth engine | No | Custom/mobile frontends |
| Sanctum | API/SPA token auth | No | APIs and SPAs |
| Jetstream | Full starter kit | Yes | Teams + 2FA on day one |
Notice what’s missing from every row: payments. None of these touch Stripe.
The Part Laravel Doesn’t Solve: Payments
Auth gets you a known user. A SaaS needs that user to pay you. With any of the packages above you still build the billing layer yourself: Cashier setup, Stripe customer creation tied to your users table, webhook handlers for subscription events, plan gating in middleware, and a customer portal for upgrades and cancellations.
Laravel Cashier is good, but it’s still a meaningful chunk of work to wire subscriptions, handle failed payments, and keep auth state and billing state in sync. That’s days, not minutes.
This is where Beag is worth a look. Beag bundles authentication and Stripe payments so you don’t assemble them from separate parts. You get server-validated auth and working subscriptions, with the user’s plan and status available to your frontend after login. Feature gating becomes a plan check instead of a billing subsystem:
if (in_array($user->plan, ['pro', 'enterprise'])) {
// grant access
}
No Cashier setup, no webhook plumbing, no syncing two systems. You keep Laravel for your app logic and skip building billing infrastructure from scratch.
What to Pick
- Shipping a normal SaaS MVP? Breeze. Edit the published controllers as needed.
- React/Vue/mobile frontend? Fortify, or Sanctum for the API layer.
- Need teams and 2FA today? Jetstream.
- Need auth and payments live this week? Use an all-in-one so identity and billing stay in sync from the start.
Add auth before your core features, not after. Every record needs an owner and every route needs a guard — doing it first means everything you build afterward is correct by default.
Add auth and Stripe payments to your Laravel app fast. Get started with Beag — no billing subsystem to build.
For more guides, see how to add auth to a Next.js app or browse the Beag blog.
Ready to Make Money From Your SaaS?
Turn your SaaS into cash with Beag.io. Get started now!
Start 7-day free trial →