Sessions, Redirects & Flash Messages
Storing state between requests, redirect-with-message and old input.
HTTP is stateless: each request arrives knowing nothing about the last one. Sessions give a visitor memory between requests, such as who's logged in, what's in a basket, or the "Saved!" message after a form submit. This lesson covers reading and writing session data, flash messages, the redirect-after-post pattern, old input, and how sessions keep logins secure.
The API routes in this app are stateless (no sessions), but every page you're reading now runs through the web middleware group, which starts a session. Code captioned with a file path is copied from this repository; blocks marked Illustrative — not in this app show common patterns.
How a session works
The driver decides where the data lives. This site uses files, set in .env:
.env
SESSION_DRIVER=file
SESSION_LIFETIME=120
config/session.php
'driver' => env('SESSION_DRIVER', 'database'),
'lifetime' => (int) env('SESSION_LIFETIME', 120),
'http_only' => env('SESSION_HTTP_ONLY', true),
'same_site' => env('SESSION_SAME_SITE', 'lax'),
file is fine on one server like this one. With several app servers behind a load balancer, use database or redis so every server sees the same sessions.
See this page's cookies
Your browser received two cookies when it loaded this page. The demo reads what JavaScript is allowed to see:
The session cookie is HttpOnly, so scripts can't read it, and a cross-site scripting bug can't steal the login. The XSRF-TOKEN cookie is deliberately readable: JavaScript HTTP clients such as Axios copy it into an X-XSRF-TOKEN header to pass CSRF checks.
Reading and writing session data
Illustrative — not in this app.
// Write
session(['last_project' => 'website-relaunch']);
$request->session()->put('filters', ['priority' => 'high']);
$request->session()->push('recently_viewed', $task->id); // append to an array
// Read
session('last_project'); // 'website-relaunch'
session('missing', 'default'); // default when absent
$request->session()->get('filters.priority'); // dot notation into arrays
$request->session()->has('filters'); // true
$request->session()->all();
// Remove
$request->session()->pull('last_project'); // read and delete
$request->session()->forget('filters');
$request->session()->flush(); // everything
A practical example: remember a user's last task filters so the list looks the same when they come back.
Illustrative — not in this app.
public function index(Request $request)
{
$filters = $request->has('priority')
? $request->only(['priority', 'status'])
: $request->session()->get('task_filters', []);
$request->session()->put('task_filters', $filters);
// ... query using $filters
}
Flash messages
Flash data lives for exactly one more request. It's built for "Task saved" messages shown after a redirect.
Illustrative — not in this app.
public function store(StoreTaskRequest $request)
{
$task = Task::create($request->validated());
return redirect()
->route('tasks.show', $task)
->with('status', 'Task created.'); // flashes 'status'
}
->with() on a redirect is shorthand for session()->flash(). Show it in the layout:
Illustrative — not in this app.
@if (session('status'))
<div class="alert alert-success">{{ session('status') }}</div>
@endif
Step through the requests to see when the message exists:
Need the message for one more request? $request->session()->reflash() keeps all flash data, and keep(['status']) keeps specific keys. session()->now('status', '...') flashes for the current request only, for when you render a view instead of redirecting.
Post/Redirect/Get
After a successful form POST, always redirect rather than returning a view. If you return a view, pressing refresh resubmits the form and creates a duplicate. The redirect makes the browser GET a new page, so refresh is harmless, and the flash message carries the "it worked" signal across.
Validation errors and old input
When validation fails on a web form, Laravel does this for you automatically: it redirects back, flashes the errors and the submitted input, and the next request can read both. That's why a form "remembers" what you typed after an error.
Illustrative — not in this app.
<input name="title" value="{{ old('title') }}">
@error('title')
<p class="error">{{ $message }}</p>
@enderror
<select name="priority">
@foreach (\App\Enums\TaskPriority::cases() as $priority)
<option value="{{ $priority->value }}" @selected(old('priority', $task->priority?->value) === $priority->value)>
{{ $priority->label() }}
</option>
@endforeach
</select>
old('title', $task->title) falls back to the saved value on an edit form. The same rules power the JSON API, which returns the errors as a 422 instead of redirecting, because API clients don't use sessions:
Sessions and logging in
Sessions are how Laravel remembers who you are after you log in. Two security steps matter:
- Regenerate the session id on login so an attacker who planted a session id before you logged in can't reuse it (session fixation).
- Invalidate the session on logout so the old id is useless.
This app's Filament admin login does both. In your own login code:
Illustrative — not in this app.
if (Auth::attempt($credentials, remember: $request->boolean('remember'))) {
$request->session()->regenerate();
return redirect()->intended('/dashboard');
}
// logout
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken(); // new CSRF token too
return redirect('/');
redirect()->intended() sends the user back to the page they tried to visit before being asked to log in. The auth middleware stores that URL in the session. The Login, Registration & Logout lesson builds the full flow.
CSRF protection
Every session holds a random CSRF token. Laravel 13's PreventRequestForgery middleware rejects POST, PUT, PATCH and DELETE requests from the browser unless they include it, so another site can't submit a form to yours using your visitor's logged-in session.
Illustrative — not in this app.
<form method="POST" action="{{ route('tasks.store') }}">
@csrf
{{-- renders <input type="hidden" name="_token" value="..."> --}}
</form>
The /api/demo routes skip this because they're in the stateless api group: no session, so no logged-in user to impersonate.
Key takeaways
- A session cookie carries only an encrypted id; the data lives server-side in the configured driver (
fileon this site). session()and$request->session()read and write data;pull()reads and deletes.- Flash data lasts one request. Use
redirect()->...->with('status', ...)after a successful form post. - Post/Redirect/Get stops refresh from resubmitting forms.
- Failed web validation flashes errors and old input automatically; use
old()and@errorin forms. - Regenerate the session on login, invalidate it on logout, and include
@csrfin every form.