After logout, a user of a Laravel Inertia app presses Back and sees the previous account's invoices; how do encryptHistory and clearHistory prevent that?
answer
- page objects live in history state
- Back restores without a request
- inertia.history.encrypt or encryptHistory()
- key in sessionStorage, needs HTTPS
- clearHistory() on logout rotates the key
basics
~20 sInertia stores each page object in the browser's history state, so Back can show it without asking the server. Encrypting history and calling Inertia::clearHistory() on logout rotates the key, so old entries cannot be decrypted and Inertia fetches fresh data instead.
solid answer
~50 sEach Inertia visit stores the page object, props included, in the browser's history state, and Back restores it from there without a request, so after logout the previous user's invoices are still readable. History encryption encrypts that state with the browser's Web Crypto API and keeps the key in sessionStorage. Turn it on globally with `inertia.history.encrypt` (`INERTIA_ENCRYPT_HISTORY`), per response with `Inertia::encryptHistory()`, or per route group with the `inertia.encrypt` middleware alias (`Inertia\Middleware\EncryptHistory`). In the logout action, call `Inertia::clearHistory()` before redirecting: the flag is kept in the session, so the next page object carries `clearHistory: true`, and the client discards the key and generates a new one. Old entries then fail to decrypt, and Inertia requests the page from the server, where the auth middleware redirects to login. It needs a secure context, since `window.crypto.subtle` is only available over HTTPS or localhost.
code
php · 23 lines<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;
class LogoutController extends Controller
{
public function __invoke(Request $request): RedirectResponse
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
Inertia::clearHistory();
return redirect('/');
}
}go deeper
Recall that Inertia keeps visited pages in browser history, so logout should clear it and sensitive apps should encrypt it.
Explain the global config, per-response call and middleware alias, and why clearHistory() must run on logout.
Reason about the session-backed flag across the logout redirect, the secure-context requirement and what encryption does not cover.
Decide where encryption is mandatory, balancing data sensitivity, shared-device risk and the cost on every history write.
## Why Back can leak data Inertia gives Back and Forward their single-page feel by saving each **page object**, with component, props and URL, in the browser's history state with `pushState`. Going Back restores that saved object and renders it without contacting the server. That is fast, but it means the props of every visited page stay in the tab after the user logs out. On a shared machine, the next person can press Back and read the previous account's invoices. ## History encryption With encryption on, the client encrypts the page object before writing it to history, using the browser's `crypto.subtle` API, and stores the key in `sessionStorage`. On Back it decrypts with that key. If decryption fails, because the key is gone or has changed, Inertia makes a fresh request for the page instead. Ways to enable it, from broad to narrow: | Scope | How | |---|---| | Whole app | `'history' => ['encrypt' => true]` in `config/inertia.php`, or `INERTIA_ENCRYPT_HISTORY=true` | | A route group | the `inertia.encrypt` middleware alias, or the `Inertia\Middleware\EncryptHistory` class | | One response | `Inertia::encryptHistory()` before returning it | | Opt out of the global setting | `Inertia::encryptHistory(false)` for that response | The page object carries `encryptHistory: true` whenever the response asks for it. ## Clearing history on logout Encryption alone is not enough: the key is still in `sessionStorage` after logout, so the old entries still decrypt. The logout action must also rotate the key: 1. The action calls `Inertia::clearHistory()`, which stores a flag in the **session**, so it survives the redirect that follows logout. 2. The next Inertia page response pulls the flag and adds `clearHistory: true` to its page object. 3. The client discards the old key and generates a new one. 4. When the user presses Back, decryption of the older entries fails, Inertia requests the page from the server, and the `auth` middleware redirects the guest to the login page. On the client, `router.clearHistory()` does the same without a server round trip. ## Requirements and limits - **Secure context.** `window.crypto.subtle` exists only on HTTPS origins and localhost, so encryption cannot work on plain HTTP. - **Same tab.** `sessionStorage` is per tab. Encryption protects the history of the tab the user logged out in. - **Order in the logout action.** `Auth::logout()`, session invalidation and token regeneration are Laravel's responsibility; the Inertia flag should be set once the session you will redirect with is in place, so the flag reaches the next page. - **Not a substitute for server checks.** Encryption only protects what is already in the browser; every protected route still needs its middleware and authorisation. - **Other copies.** It does not reach HTTP caches or anything the page itself wrote to storage. ## Verifying it works 1. Log in over HTTPS, visit a few invoice pages, and inspect `history.state` in the console: with encryption on, the stored page is an encrypted value rather than readable props. 2. Log out and check that the next page object carried `clearHistory: true`. 3. Press Back: the network tab should show a request for the old page, answered by a redirect to the login page, instead of an instant render of old data. 4. Repeat without `clearHistory()` to see why encryption alone is not enough. ## When to use it - Apps with personal or financial data, shared-device use, or compliance requirements: enable globally and clear on logout. - Apps with a few sensitive areas: apply the middleware to those route groups and still clear on logout. - Public content: not needed; plain history keeps Back fast with no crypto work.
- Why is Inertia::clearHistory() stored in the session rather than on the response?Logout usually ends with a redirect, so the response that triggers the rotation is not the one the action returns. Storing the flag in the session lets the next Inertia page response, after the redirect, pull it and add `clearHistory: true` to its page object. The client then rotates the key when it renders that page.
- Why does history encryption fail on a plain HTTP staging site?It relies on `window.crypto.subtle`, which browsers expose only in secure contexts: HTTPS origins and localhost. On plain HTTP the API is missing, so encryption cannot run there. Serve staging over HTTPS if it must behave like production.
A diary kept in a shared drawer is locked, and the key sits in the owner's coat pocket. Encryption is the lock; clearHistory on logout swaps the lock for a new one, so the next person cannot read the old pages and has to ask the owner, the server, for them again.
saying these in an interview costs you the question
- Back after logout always hits the server, so the auth middleware protects it.
- Encrypting history alone hides old pages after logout.
- The encryption key is stored in a cookie shared with Laravel.
- clearHistory() must be the last thing sent in the logout response itself.
- History encryption also protects data cached by the HTTP layer.