Chapter 37 — Authentication in Front-End
Section 1 — What authentication is
Authentication is the process of checking who someone is before giving them access to something that belongs to them, such as an account, a private page or a personal setting. In everyday life, showing an identity card or unlocking a phone with a passcode are forms of authentication: they answer the question, are you really the person you claim to be. On the web, a login form plays a similar role, asking for evidence, such as a password, that only the account holder should know. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
The front-end part of authentication is the interface: the form, the buttons, the messages and the screens that guide a person through proving who they are. The actual checking, however, must happen on the server, because anything that runs in a browser can be inspected and changed by the visitor. A front-end developer therefore builds a clear, honest login experience, and leaves the decision about whether the evidence is correct to the server, which is the only part that can be trusted with that decision. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A login screen that asks for an email address and a password, explains what happens next, and shows a clear error if the details do not match, is the familiar front-end shape of authentication.
<form><label for="email">Email</label><input id="email" type="email"><label for="password">Password</label><input id="password" type="password"><button>Sign in</button></form>
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is treating a front-end check, such as hiding a button, as proof that someone is authenticated, so build the login interface clearly, but always let the server decide whether the evidence is correct. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, look at a login screen and identify which parts are interface and which decision must be made by the server. A good check is specific: it names what should happen, what you observed, and whether the two match. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked.
Check: What question does authentication answer?
Section 2 — Authentication and authorisation
Authentication and authorisation sound similar, but they answer different questions. Authentication asks, who are you, while authorisation asks, what are you allowed to do now that we know who you are. A person can be authenticated as a genuine account holder yet still not be authorised to view another person's private page, to change an administrator's settings, or to delete content they do not own. Confusing the two is a common source of both poor design and serious mistakes. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer.
In the front end, this distinction shows up in what you display: you might show a signed-in visitor their own dashboard, while hiding administrator tools they are not allowed to use. Hiding a tool, however, is only a matter of presentation; it does not enforce the rule. Enforcement must happen on the server, which checks, for every request, both that the visitor is authenticated and that they are authorised for that specific action. The front end's job is to present the right options clearly, not to grant permission by itself. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system.
A signed-in reader can see their own saved articles, but does not see an editor's publish button, because their role does not include publishing.
<button>Save article</button>
The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content. After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts.
A common mistake is assuming that hiding a button from a visitor means they cannot perform the action, so present only appropriate options in the interface, and remember that the server must enforce every permission. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first.
To check your own work, pick a feature in an app and state, separately, who may use it and what they are allowed to do with it. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked. Write down what you checked and what you found, especially when the result surprises you; that record becomes useful evidence later.
Check: What is the difference between authentication and authorisation?
Section 3 — Why the front end never trusts itself
A browser runs on the visitor's own device, which means the visitor can inspect its code, change its values, bypass its checks and send requests that the interface never offered. That is not a flaw in a particular browser; it is the nature of code that runs on someone else's machine. Any rule that exists only in front-end code, such as a check that a user is signed in, a check that a value is within a limit, or a check that a button is hidden, can therefore be bypassed by a determined visitor in minutes. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer.
This does not make front-end work pointless; it clarifies its role. Front-end checks improve usability, by catching ordinary mistakes early and giving immediate feedback, but they are never security. Security decisions, such as whether a person may view, change or delete something, must be made again on the server, where the visitor cannot change the rules. A good front-end developer builds helpful, honest interfaces, and designs them with the assumption that the server will verify everything again. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system.
A form that checks a password is not empty before submitting still relies on the server to check whether the password is correct.
<input id="password" type="password" required>
The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content. After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts.
A common mistake is believing that a front-end validation or a hidden button is a security control, so use front-end checks for usability and feedback, and always expect the server to verify every important decision again. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first.
To check your own work, list three checks in a front-end form and state, for each, what the server must still verify. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked. Write down what you checked and what you found, especially when the result surprises you; that record becomes useful evidence later.
Check: Why can front-end checks not be trusted for security?
Section 4 — The login flow, step by step
A login flow is the sequence of steps a person follows to prove who they are and reach their account. At concept level, it begins with the person opening a login screen, continues with them providing evidence, such as a password, and ends with the server confirming that evidence and establishing a signed-in state for subsequent requests. The front end's role is to guide the person through those steps clearly, to show progress and errors honestly, and to handle the result the server returns, whether success or failure. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer.
In a typical flow, the person enters their details and submits the form, the browser sends those details securely to the server, the server checks them, and if they match, it establishes a session, which is covered in a later section. The front end then shows the signed-in view, such as a dashboard or a welcome message, and offers a way to sign out. If the details do not match, the front end shows a clear, non-specific error, without revealing whether the email or the password was the wrong part, for reasons covered in the section on error messages. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system.
A login screen that, on success, shows a welcome message and a sign-out button, and on failure, shows a single clear error, follows the basic flow honestly.
<form><input type="email" placeholder="Email"><input type="password" placeholder="Password"><button>Sign in</button></form>
The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content. After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts.
A common mistake is showing different errors for a wrong email and a wrong password, which leaks information, so show one clear, general error on failure, and guide the visitor to the next step, such as password reset, only through the proper flow. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first.
To check your own work, sketch a login flow on paper, from opening the screen to seeing the signed-in view, and mark which step the server decides. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked. Write down what you checked and what you found, especially when the result surprises you; that record becomes useful evidence later.
Check: What are the steps of a login flow?
Section 5 — The logout flow
Logout is the process of ending a signed-in state, so that the account is no longer accessible from that browser or device. It is just as important as login, because a session left open on a shared computer, a borrowed phone or a public device can let the next person use the account as if they were its owner. A clear, reliable logout is therefore a basic courtesy and a basic safety feature, not an optional extra that can be added later. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
At concept level, logout should tell the server to end the session, so that the session can no longer be used, and then return the visitor to a signed-out view, such as the home page or the login screen, with a clear confirmation. The front end should not merely hide signed-in content while leaving the session alive, because that would look signed out while still being usable. It should also clear any signed-in display, such as a name or avatar, so the next person sees a neutral, signed-out page. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A signed-in page with a clearly labelled Sign out button, which on use returns the visitor to the home page with a signed-out message, follows the logout flow properly.
<button>Sign out</button><p>You have been signed out.</p>
Try changing one small thing in the example and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, reuse and understand.
A common mistake is only hiding signed-in content on logout while leaving the session usable, so make logout end the session on the server, show a signed-out view, and clear any signed-in display. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, use an app's sign-out button and check that you land on a signed-out view and cannot return to private pages without signing in again. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: What should logout actually do?
Section 6 — Sessions in plain words
A session is the server's way of remembering that a visitor who signed in earlier is the same visitor making a new request now, without asking for their password on every single page. When a person signs in successfully, the server creates a session, gives the browser a session identifier, and remembers that identifier alongside the account it belongs to. On each later request, the browser presents the identifier, and the server checks it, so the visitor stays signed in as they move around the site. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
The front end does not need to understand the session's internal details, and it must never try to create, forge or edit a session identifier itself. Its job is to present the signed-in or signed-out state that the server reports, to handle the case where a session has expired, and to offer sign-in and sign-out at the right moments. Sessions have a lifetime, after which they expire, and they can be ended early by logout, both of which are server decisions that the front end must respect and reflect honestly. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A visitor who signs in, browses three pages and is still recognised on each one is experiencing a session, without ever re-entering their password.
<p>Welcome back, Ada. You are signed in.</p><button>Sign out</button>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is trying to invent or edit a session identifier in front-end code, so treat the session as the server's responsibility, present the state it reports, and handle expiry gracefully. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, sign in to a site, browse several pages, and note how the site recognises you without asking for your password each time. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: What is a session?
Section 7 — Cookies in plain words
A cookie is a small piece of data that a server asks a browser to store and to send back with later requests to the same site. Cookies are commonly used to carry a session identifier, so the server can recognise a returning visitor, and they can also carry preferences, such as a language choice. The important point for front-end developers is that the safest cookies are set by the server with protections that keep them out of reach of page scripts, which limits what a malicious script could steal if it ever ran on the page. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
Two protections matter most at concept level. A cookie marked HttpOnly cannot be read by JavaScript on the page, which means page scripts cannot copy it. A cookie marked Secure is only sent over an encrypted connection, and a SameSite setting controls whether the cookie is sent with requests that come from other sites, which helps against certain cross-site tricks. Front-end code should not try to set, read or manage session cookies itself; it should let the server set them, and it should respect the signed-in state the server reports. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A server-set session cookie, marked HttpOnly and Secure, lets the browser carry the session identifier without exposing it to page scripts.
Server sets cookie: HttpOnly, Secure, SameSite=Lax (concept, not front-end code)
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is reading or writing session cookies from front-end scripts, so leave session cookies to the server, with HttpOnly and Secure protections, and present the state the server reports. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, check a site's sign-in behaviour and explain, in plain words, who sets the session cookie and who can read it. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: Why should session cookies be HttpOnly?
Section 8 — Tokens in plain words
A token is a credential, a kind of digital pass, that proves a visitor has been authenticated, and that a server can check without looking up a password on every request. Tokens are often used by applications that talk to an application programming interface, where the front end sends the token with a request and the server verifies it before answering. At concept level, a token is like a ticket: it proves you were admitted, it has a limited lifetime, and it can be revoked, but anyone who copies it can use it until it expires or is revoked. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
That last point is why token handling is so sensitive. A token is a secret, and a secret that leaks can be misused by whoever finds it, even if they never knew the password. Front-end code must therefore never contain tokens, never embed them in a page, and never treat a token as ordinary display data. Where an application uses tokens, the safer designs keep them managed by the server, or in a form the page's scripts cannot read, rather than handing a long-lived token to front-end code to look after. The details belong to the server design; the front-end lesson is that a token is a credential, not a piece of content. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A request that proves a visitor is signed in by presenting a token the server can verify, without re-sending the password each time, illustrates the token idea.
Concept: server verifies token, front end never embeds or displays it
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is embedding a token in front-end code, or displaying it on a page, so treat every token as a secret, never put it in front-end code, and prefer server-managed sessions where possible. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, explain, in plain words, why copying a token is as serious as copying a password, even though the two are not the same thing. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: What is a token, and why is it sensitive?
Section 9 — Access and refresh tokens
Some systems use two kinds of token together: a short-lived access token, which is presented with each request to prove the visitor is signed in, and a longer-lived refresh token, which is used only to obtain a new access token when the old one expires. The idea is to limit damage: because the access token lives only briefly, a copied access token is useful only for a short time, while the refresh token, which is more powerful, is used rarely and is kept under stricter protection. Together, they balance convenience, because visitors are not asked to sign in constantly, with safety, because no single token lasts forever. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
For front-end developers, the concept matters more than the mechanics. The front end should not be responsible for storing, rotating or protecting these tokens as if they were ordinary data, and it should handle expiry by asking the server for a fresh session through the proper flow, rather than by inventing its own token logic. When an access token expires, the honest front-end response is to show a loading or re-authentication state, and to let the server decide whether the session can be renewed or whether the visitor must sign in again. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A visitor who browses for an hour without re-entering a password, while short-lived tokens expire and are renewed behind the scenes, is experiencing this two-token idea.
Concept: short-lived access token, longer-lived refresh token, both managed by the server
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is storing tokens in front-end code or in browser storage as if they were ordinary data, so leave token storage and renewal to the server design, and handle expiry with a clear re-authentication state. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, explain, in plain words, what a short-lived token limits, and why a longer-lived token needs stricter protection. A good check is specific: it names what should happen, what you observed, and whether the two match. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked.
Check: Why use two kinds of token?
Section 10 — Secrets must never live in front-end code
This is the most important rule in this chapter, stated without qualification: secrets must never live in front-end code. Passwords, tokens, application programming interface keys, private keys and any other credential must not be written into HTML, CSS, JavaScript, or any file that is sent to a browser, because every file sent to a browser can be read by anyone who opens the page and inspects its source. A secret in front-end code is not hidden; it is published, whether you intended to publish it or not. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
The reason is simple. Front-end code is delivered to the visitor's device, and the visitor can view it, copy it and search it. There is no front-end hiding technique, no obfuscation and no minification that turns a published secret into a safe secret; those techniques only make the secret slightly harder to spot at a glance. If a front-end feature needs a secret, the correct design is to keep the secret on the server, let the server perform the action that needs the secret, and let the front end talk only to the server, never directly with the secret itself. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A page that needs to send an email, charge a payment or call a private service should ask its own server to do it, keeping the relevant key on the server only.
Front end asks server; server uses secret; secret never reaches browser
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is pasting an API key, token or password into front-end code, even temporarily, so keep every secret on the server, let the server perform secret actions, and remove any secret that was ever published, by revoking it. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, inspect a front-end file and check that it contains no passwords, tokens, keys or other credentials of any kind. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: What happens to a secret written into front-end code?
Section 11 — Why browser storage is not safe for tokens
Browser storage, such as localStorage and sessionStorage, is a convenient place for a page to keep ordinary, non-sensitive data, such as a display preference or a draft that is not private. It is not a safe place for tokens, passwords or other credentials, and this chapter states that plainly, because the opposite advice is common and dangerous. Any script running on the page can read browser storage, which means that if a malicious script ever runs on the page, for example through a cross-site scripting flaw covered later, it can copy every token stored there. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent.
The distinction is between data and credentials. A theme preference leaking is an inconvenience; a token leaking is an account takeover, because the token can be used to act as the account holder until it expires or is revoked. For that reason, authentication credentials should be kept in server-managed, script-inaccessible forms, such as HttpOnly cookies set by the server, rather than in storage that page scripts can read. If you see a tutorial that stores a token in browser storage and calls it safe, treat that tutorial as wrong on this point, however helpful it is otherwise. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened.
A page that stores only a theme preference in browser storage, while its session is managed by a server-set cookie, follows the safe pattern.
Browser storage: theme preference only; session: server-set HttpOnly cookie
After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts. If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved.
A common mistake is storing a login token in browser storage and calling it safe, so keep credentials out of browser storage entirely, use server-managed sessions, and treat any contrary tutorial advice as unsafe. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix.
To check your own work, list what a page stores in browser storage, and check that none of it is a token, password or other credential. Write down what you checked and what you found, especially when the result surprises you; that record becomes useful evidence later. The goal of checking is confidence you can defend, not a quick glance that lets you move on.
Check: Why is browser storage unsafe for tokens?
Section 12 — Password handling in the front end
Passwords are among the most sensitive data a front end ever handles, and the front-end rules for them are strict and simple. A password should be entered in a password field, sent securely to the server over an encrypted connection, and never stored, logged, displayed or embedded in front-end code. The front end should not keep a copy of the password after submission, should not show it in plain text except when the visitor explicitly asks to reveal it, and should not send it to any destination other than the server that will check it. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
Good password interfaces help visitors without weakening safety. They use a password field that masks input by default, offer a clearly labelled show-password option for visitors who want to check their typing, support password managers and autofill, and give clear feedback about requirements without revealing anything about other accounts. They never pre-fill a password from front-end code, never store a password in browser storage, and never include a password in a page address, because addresses are logged, shared and bookmarked. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A sign-in form with a masked password field, a show-password toggle, and a forgotten-password link follows the basic pattern.
<label for="password">Password</label><input id="password" type="password" autocomplete="current-password"><button type="button">Show</button>
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is storing a password in front-end code, in browser storage, or in a page address, so mask passwords by default, offer a reveal toggle, support password managers, and send passwords only to the checking server over an encrypted connection. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, inspect a login form and check that its password field masks input, supports autofill, and is not stored anywhere in the page. A good check is specific: it names what should happen, what you observed, and whether the two match. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked.
Check: What should a front end never do with a password?
Section 13 — Showing login state in the interface
Login state is what the interface shows about whether a visitor is signed in, and it must always reflect what the server reports, not what the front end guesses. A signed-in visitor might see their name, an avatar, a dashboard link and a sign-out button, while a signed-out visitor sees sign-in and register links instead. The difference matters because a visitor who believes they are signed in, when they are not, may try to save work that is then lost, and a visitor who believes they are signed out, when they are not, may leave an account open on a shared device. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
Build the interface so it has a clear signed-in view and a clear signed-out view, and switch between them only when the server confirms the change, such as after a successful sign-in, a logout, or a session expiry. Avoid showing private information, such as a full name, email address or account details, on shared or public screens where it is not needed, and avoid flashing a signed-in view briefly before the server confirms, which can mislead both visitors and screenshots. When the state is unknown, for example while the page is still checking, show a neutral loading state rather than guessing. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A page that shows a neutral loading message while it checks, then a welcome message and sign-out button once signed in is confirmed, handles login state honestly.
<p>Checking your sign-in status…</p>
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is guessing login state in the front end, or flashing a signed-in view before the server confirms, so show a neutral state while checking, switch views only on server confirmation, and keep private details off public screens. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, load a signed-in page slowly and check that it does not flash private content before the server confirms you are signed in. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: Why must login state reflect the server, not a guess?
Section 14 — Protected pages and routes
A protected page is a page that should only be seen by signed-in visitors, or by visitors with a particular role, such as a personal dashboard, an account settings page or an editor's workspace. In a front-end application, routes, meaning the addresses that show different views, often include protected routes that check the visitor's state before showing their content. The front-end check is useful for guidance: it can redirect a signed-out visitor to the login screen instead of showing a broken or empty dashboard. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks.
The crucial limit, repeated throughout this chapter, is that a front-end route check is guidance, not enforcement. A visitor can bypass a front-end redirect by requesting the underlying data directly, so the server must check authorisation for every piece of private data, on every request, regardless of what the front end displayed. A protected route that shows a login screen to a signed-out visitor, while the server refuses private data to anyone unauthorised, is the correct combination of a helpful interface and real enforcement. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once.
A dashboard route that shows the login screen to a signed-out visitor, and the dashboard only after the server confirms the visitor is signed in, follows the pattern.
Route: /dashboard — signed out: show login; signed in: show dashboard (server confirms)
If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved. Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape.
A common mistake is treating a front-end route redirect as the only protection for private data, so use route checks to guide visitors, and rely on the server to enforce access to every private resource. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult.
To check your own work, try to reach a protected page while signed out, and check that you are guided to sign in rather than shown private content. The goal of checking is confidence you can defend, not a quick glance that lets you move on. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea.
Check: What does a protected route do, and what does it not do?
Section 15 — Redirects after login
After a successful login, many applications return the visitor to the page they were trying to reach before they were asked to sign in, rather than always sending them to a generic home page. That is a good experience when done carefully: a person who clicked a link to their settings, was asked to sign in, and then lands on their settings feels that the application understood their goal. A careless redirect, however, can send a visitor to an untrusted address, which is a classic trick for phishing, where a convincing login page sends the victim onward to a malicious site. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for.
The safe rule is to redirect only to addresses within your own site, and only to addresses that make sense as destinations, such as a dashboard or the page originally requested on the same site. Validate the requested destination on the server, ignore or reject destinations that point elsewhere, and never trust a destination supplied in a page address without checking it. The front end can remember the intended page, but the server must approve the final destination, just as it approves the login itself. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug.
A visitor who tried to open their settings, signed in, and landed back on their settings has experienced a safe post-login redirect.
Requested: /settings — after sign-in: return to /settings (same site only)
Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape. Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone.
A common mistake is redirecting after login to any address supplied in a link, including addresses on other sites, so allow redirects only to safe addresses on your own site, and let the server approve the destination. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form.
To check your own work, sign in from a link to a protected page and check that you land on that same-site page, not on an external address. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: Why must post-login redirects be validated?
Section 16 — Remember me, safely
Remember me is a feature that lets a visitor stay signed in across browser sessions, so they do not have to sign in every time they return. At concept level, it works by giving the browser a longer-lived session credential, managed by the server, which the server can check on a later visit and can revoke if needed. It is a convenience feature, and like all convenience features in authentication, it trades a little safety for a little ease, which is why it must be designed carefully and offered as a choice, not as a default that cannot be turned off. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
The front-end part is simple: a clearly labelled checkbox, unchecked by default on shared or public devices, with plain wording about what it does. The safety part belongs to the server: the long-lived credential must be protected, must expire, must be revocable, and must never be a password or a token stored in front-end code or in browser storage. Visitors should also be able to see and end remembered sessions from their account settings, so a remembered session on a lost device can be revoked without changing the password alone being the only option, where the system supports it. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A login form with an unchecked Remember me checkbox, explained in plain words, lets the visitor choose convenience deliberately.
<label><input type="checkbox"> Remember me on this device</label>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is remembering every visitor by default, or storing a remember-me credential in front-end code, so offer remember me as an unchecked choice, keep the credential server-managed and revocable, and let visitors end remembered sessions. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, find a remember-me option, check whether it is unchecked by default, and explain in plain words what choosing it would do. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: What does remember me actually remember?
Section 17 — Password reset flow
A password reset flow lets a person who has forgotten their password regain access to their account, without the system ever revealing the old password, because a well-designed system does not store passwords in a form that can be revealed. At concept level, the person asks for a reset, the server sends a time-limited, single-use reset link to the email address registered to the account, the person follows that link, and they choose a new password. The front end's role is to guide each step clearly and to handle errors honestly, without leaking information about which accounts exist. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for.
The front-end screens are straightforward: a request form that asks for an email address, a confirmation message that does not reveal whether that address has an account, a reset form that asks for a new password twice, and a success message that invites the person to sign in with the new password. The reset link itself must be treated as a secret, because anyone with the link can change the password, so it should expire quickly, work only once, and never be shown in front-end code or in a page address that is shared. After a reset, the person should be asked to sign in with the new password, rather than being silently signed in on a device they may not control. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug.
A reset request that replies, if an account exists, a reset link has been sent, without confirming or denying the account, follows the safe pattern.
<p>If an account exists for that email, a reset link has been sent.</p>
Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape. Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone.
A common mistake is revealing whether an email has an account, or showing a reset link in the interface, so show a neutral confirmation, treat reset links as short-lived secrets, and ask for sign-in with the new password afterwards. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form.
To check your own work, request a password reset for an address and check that the message neither confirms nor denies that account. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea.
Check: Why should a reset message not confirm whether an account exists?
Section 18 — Email verification flow
Email verification is the process of confirming that the email address a person gave when registering actually belongs to them, usually by sending a message to that address with a link or a code that only the owner of the inbox can use. It matters because many account actions, including password resets and important notifications, rely on that email address being correct, and an unverified address could belong to someone else entirely. Verification is therefore a trust step, not a mere formality. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
In the front end, the flow is: the person registers, sees a message explaining that a verification message has been sent and what to do next, follows the link or enters the code from their inbox, and then sees a confirmation that their address is verified. Until verification is complete, the application may limit what the account can do, and it should explain that limit plainly rather than leaving the visitor confused about why a feature is unavailable. As with password resets, verification links and codes are secrets, and they must never appear in front-end code or be shared in screenshots or messages. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A registration that ends with a clear message, check your inbox for a verification link, followed by a confirmed screen after the link is used, follows the flow.
<p>Check your inbox for a verification link to confirm your email address.</p>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is treating an unverified email address as fully trusted, or displaying verification links in the interface, so explain verification clearly, limit unverified accounts honestly, and treat verification links as secrets. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, register a test account of your own, follow its verification flow, and note what changes once verification is complete. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: What does email verification prove?
Section 19 — Two-factor authentication
Two-factor authentication adds a second, independent piece of evidence to the login, beyond the password, so that a stolen password alone is not enough to take over an account. The second factor is usually something the person has, such as a phone that generates a time-based code, or a security key, or something the person is, in biometric systems. The idea is that an attacker would need both the password and the second factor, which is much harder than obtaining one alone, especially when the second factor is a physical device. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
In the front end, two-factor login adds a step after the password: a screen that asks for the current code, or that waits for a security key, with clear instructions and a way to use a backup method if the main method is unavailable. That backup method, such as recovery codes covered later, must be handled with the same care as a password. The front end should not store, display or log second-factor codes, should not accept a code more than once where the system forbids reuse, and should give clear errors without revealing whether the password or the code was wrong, to avoid leaking information. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A login that, after a correct password, asks for a six-digit code from an authenticator app, with a link to use a recovery code instead, follows the two-factor pattern.
<label for="code">Enter the code from your authenticator app</label><input id="code" inputmode="numeric">
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is storing or displaying second-factor codes in front-end code, or skipping the second step for convenience, so add a clear second-factor step, handle backup methods carefully, and never store or display codes. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, sign in to an account with two-factor authentication enabled and note each step, and what happens if one step is wrong. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: Why does a second factor help, even if a password is stolen?
Section 20 — Social login in plain words
Social login lets a person sign in to a site using an account they already have with a large provider, such as a Google, Apple or GitHub account, instead of creating a new password for every site. At concept level, the site redirects the visitor to the provider, the visitor signs in there and approves the site's request, and the provider tells the site, in a secure way, that this visitor is authenticated, without ever giving the site the visitor's provider password. The site then creates or links its own account for that visitor. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for.
For front-end developers, the important rules are about honesty and secrets. The social login button must clearly name the provider, must not pretend to be the provider's own sign-in page, and must not collect the visitor's provider password on your site. All secret keys and exchanges between your server and the provider belong on the server, never in front-end code, because a key in front-end code would be visible to anyone. After social login, the site should still offer its own account settings, including a way to disconnect the provider and a way to sign in another way if the provider becomes unavailable. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug.
A button that says Continue with Google, which takes the visitor to Google's own sign-in page and then returns them to the site, follows the social login pattern.
<button>Continue with Google</button>
Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape. Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone.
A common mistake is collecting a provider password on your own page, or putting a provider secret in front-end code, so name the provider clearly, let the provider handle its own sign-in, and keep all provider secrets on the server. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form.
To check your own work, look at a social login button and check that it names the provider and leads to the provider's own sign-in, not a copy on the site. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: Why should a site never ask for your provider password itself?
Section 21 — Single sign-on in plain words
Single sign-on is a system in which signing in once gives access to several related applications, without signing in separately to each one. A workplace that uses one sign-in for email, documents and internal tools is a familiar example: the person signs in once, a trusted identity system confirms who they are, and each application accepts that confirmation. The benefit is convenience and consistency, because there is one account, one password policy and one place to enable extra protections such as two-factor authentication. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
From a front-end perspective, single sign-on mostly looks like an ordinary sign-in that redirects to a central sign-in page and then returns the visitor to the application, already signed in. The front end should present that flow clearly, explain where the visitor is being sent and why, and handle the return gracefully, including the case where the central sign-in fails or the visitor cancels. As with social login, all secrets and trust decisions belong to the server and to the identity system, never to front-end code, and the front end must not try to forge or invent a single sign-on confirmation. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A workplace app that sends a visitor to the organisation's central sign-in page, then returns them to the app signed in, illustrates single sign-on.
<p>You will sign in through your organisation's sign-in page.</p>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is forging a sign-on confirmation in front-end code, or hiding where a visitor is being sent, so explain the redirect plainly, handle cancellation and failure gracefully, and leave trust decisions to the server and identity system. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, explain, in plain words, how signing in once can give access to several apps, and where the trust decision is made. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: What is single sign-on?
Section 22 — Lockouts and throttling
Lockouts and throttling are server protections against guessing attacks, in which an attacker tries many passwords in the hope of finding the right one. Throttling slows down repeated attempts, for example by adding a delay or by limiting how many attempts can be made in a period, while a lockout temporarily blocks an account, or an address, after a number of failed attempts. Both make guessing far less practical, because an attacker who could try thousands of passwords per minute is reduced to a handful, and the account holder is given time to notice and respond. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent.
The front end's role is to present these protections honestly, without helping an attacker. When an account is locked or throttled, the interface should show a clear message, such as too many attempts, please try again later, and offer a legitimate recovery path, such as password reset, without revealing extra detail. The front end must not try to bypass a lockout by changing its own behaviour, must not tell the visitor exactly how many attempts remain in a way that helps guessing, and must not implement its own lockout in front-end code, because a front-end lockout can be bypassed by anyone who ignores the interface. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened.
A login screen that, after many failed attempts, shows a too-many-attempts message and a password-reset link, is presenting a server lockout honestly.
<p>Too many attempts. Please try again later, or reset your password.</p>
After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts. If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved.
A common mistake is implementing a lockout only in front-end code, which an attacker can bypass, so present server lockouts clearly, offer legitimate recovery, and never try to bypass or replace server protections. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix.
To check your own work, explain, in plain words, how throttling changes the maths of guessing a password. Write down what you checked and what you found, especially when the result surprises you; that record becomes useful evidence later. The goal of checking is confidence you can defend, not a quick glance that lets you move on.
Check: Why slow down or block repeated login attempts?
Section 23 — Error messages that do not leak
Login error messages are a place where a small wording choice can leak useful information to an attacker. A message that says this email does not exist tells an attacker which addresses have accounts, while a message that says the password is wrong, after accepting the email, tells an attacker that the email was correct and only the password remains to be guessed. Attackers can use those leaks to build lists of real accounts and to focus their guessing, which is why safe systems use a single, general error for failed logins. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent.
The safe front-end pattern is one message for all login failures, such as email or password incorrect, regardless of which part was wrong, and the same message whether the account exists or not. That may feel less helpful to a genuine visitor who mistyped their email, but it protects every account equally, and the visitor still has legitimate recovery paths, such as password reset and account help, which do not require the login screen to reveal account existence. Apply the same principle to registration and reset screens, where confirming that an email is already registered can also leak information. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened.
A failed login that always shows the same general message, whether the email or the password was wrong, follows the safe pattern.
<p>Email or password incorrect. Please try again.</p>
After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts. If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved.
A common mistake is showing different errors for a missing account and a wrong password, so use one general error for all login failures, and offer recovery paths that do not leak account existence. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix.
To check your own work, try a failed login with a wrong email and a wrong password, and check that both show the same message. Write down what you checked and what you found, especially when the result surprises you; that record becomes useful evidence later. The goal of checking is confidence you can defend, not a quick glance that lets you move on.
Check: Why use one general error for failed logins?
Section 24 — Front-end validation and server validation
Validation means checking that input is acceptable before it is used, and in authentication it happens in two places for two different reasons. Front-end validation checks input in the browser, giving immediate feedback, such as telling a visitor that an email address looks incomplete before they submit the form. Server validation checks the same input again, on the server, and it is the check that actually matters for safety, because front-end validation can be bypassed by anyone who ignores the interface. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks.
A good login or registration form therefore does both, and does not confuse them. The front end validates for usability: it checks format, completeness and obvious mistakes, and it explains them in plain words next to the field. The server validates for safety and for truth: it checks that the account exists, that the password matches, that the email is acceptable, and that the request is authorised. If the server rejects input that the front end accepted, the front end must display the server's error clearly, rather than hiding it or showing a generic success message. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once.
A registration form that warns immediately about an incomplete email, and still shows the server's error if the email is already in use, uses both kinds of validation.
<label for="email">Email</label><input id="email" type="email" required><p>Please enter a complete email address.</p>
If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved. Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape.
A common mistake is treating front-end validation as sufficient, and ignoring or hiding server errors, so validate in the front end for usability, validate again on the server for safety, and display server errors clearly. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult.
To check your own work, submit a form with an obvious mistake, then with a mistake only the server can detect, and check that both are handled clearly. The goal of checking is confidence you can defend, not a quick glance that lets you move on. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea.
Check: What is front-end validation for, if the server checks again?
Section 25 — Cross-site request forgery in plain words
Cross-site request forgery, often shortened to CSRF, is an attack in which a malicious site tricks a visitor's browser into sending a request to a site where the visitor is already signed in, as if the visitor had intended it. Because the browser automatically sends the site's cookies with the request, the target site may treat the forged request as genuine, for example changing a setting, posting a message or transferring something, even though the visitor never meant to do it. The attack works not by stealing a password, but by abusing the trust the site places in the visitor's signed-in browser. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks.
Front-end developers need to understand CSRF at concept level, because the protections are a partnership between front end and server. The server issues a unique, unpredictable token for each form or session, the front end includes that token with requests that change something, and the server checks it before acting. The SameSite cookie setting, covered earlier, also helps, by limiting when cookies are sent with cross-site requests. The front-end lesson is to send state-changing requests through proper forms or requests that include the server's protection, and never to perform a sensitive change through a simple link that a malicious page could trigger by loading it as an image or a link. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once.
A settings form that includes a hidden, server-issued token, which the server checks before saving a change, illustrates the CSRF protection pattern.
<form><input type="hidden" name="csrf" value="server-issued-value"><button>Save settings</button></form>
If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved. Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape.
A common mistake is performing sensitive changes through simple links, or omitting the server's CSRF protection, so include the server-issued protection with state-changing requests, and use SameSite cookies as an additional layer. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult.
To check your own work, explain, in plain words, how a malicious site could trick a signed-in browser into making an unwanted request. The goal of checking is confidence you can defend, not a quick glance that lets you move on. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea.
Check: What is a cross-site request forgery attack?
Section 26 — Cross-site scripting and authentication
Cross-site scripting, often shortened to XSS, is an attack in which a malicious script is made to run on a page where it does not belong, for example through a comment, a profile field or a search result that is displayed without proper escaping. Once a malicious script runs on a page, it runs with that page's privileges, which means it can read page content, act as the visitor, and copy anything page scripts can read. That is exactly why tokens and credentials must never be stored where page scripts can read them, as covered earlier: an XSS flaw turns readable storage into stolen credentials. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
The front-end defence is to treat all untrusted data as text, not as code. When displaying user-supplied content, render it as text, escape it properly, and never insert it into the page as raw HTML unless you have a safe, deliberate reason and a trusted sanitisation step. Be careful with fields that accept rich input, with addresses and parameters that are reflected on the page, and with any code that builds HTML by joining strings together. The server and the browser's content policies also help, but the front-end habit of never trusting displayed data as code is the daily defence. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A comment displayed as plain text, even if it contains characters that look like HTML tags, is safely handled, because the browser shows it rather than running it.
<p>Comment: I love this <script> trick</p>
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is inserting user-supplied content into a page as raw HTML without escaping, so display untrusted data as text, escape it properly, and never build HTML by joining untrusted strings. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, find a place where user content is displayed, and check that it is shown as text, not run as code. A good check is specific: it names what should happen, what you observed, and whether the two match. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked.
Check: Why does XSS make readable token storage so dangerous?
Section 27 — HTTPS and authentication
Authentication over an unencrypted connection is unsafe, because passwords, session identifiers and other credentials travel in a form that anyone positioned between the visitor and the server could read or alter, for example on a public Wi-Fi network. HTTPS encrypts the connection, so the conversation between browser and server is protected in transit, and it also helps the browser verify that it is talking to the genuine site, not an impostor. For that reason, login, registration, password reset and any signed-in page should always use HTTPS, without exception. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent.
The front-end part is to ensure every authentication-related address, form and link uses the secure form, and to avoid mixing secure and insecure content on signed-in pages, which can weaken the protection. If a site is accessed over plain HTTP, the browser will warn visitors, and many modern features, including secure cookies, will not work as intended. There is no legitimate reason to run authentication over plain HTTP in a real application, even if the site seems small or unimportant, because the credentials involved are always sensitive. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened.
A login form whose address begins with https, and whose browser shows a padlock, is the expected pattern for authentication.
<form action="https://example.com/sign-in">
After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts. If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved.
A common mistake is running login or signed-in pages over plain HTTP, or mixing secure and insecure content, so use HTTPS for every authentication page and form, and treat any plain-HTTP login as a fault to fix before use. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix.
To check your own work, open a login page and check that its address uses HTTPS and that no insecure content warnings appear. Write down what you checked and what you found, especially when the result surprises you; that record becomes useful evidence later. The goal of checking is confidence you can defend, not a quick glance that lets you move on.
Check: Why must authentication always use HTTPS?
Section 28 — Session expiry and idle timeout
Session expiry means a signed-in state ends after a set time, even if the visitor does not explicitly sign out, and idle timeout means it ends after a period of inactivity. Both exist because a session left open indefinitely is a risk, especially on shared devices, and because a stolen session identifier should stop working after a while rather than remaining useful forever. Expiry is therefore a safety feature, not a nuisance designed to annoy visitors, although it must be handled gracefully so it does not feel like one. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
The front end's job is to handle expiry clearly. When a session expires, the visitor should be told plainly that their session has ended, be offered a simple way to sign in again, and, where possible, be protected from losing work, for example by saving a draft before the session ends or by warning before expiry. The front end should not try to extend a session secretly, should not store credentials to re-sign-in automatically without the server's proper flow, and should not pretend an expired session is still valid by showing stale private content. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A banking-style page that warns you will be signed out soon, then signs you out after inactivity and invites you to sign in again, follows the expiry pattern.
<p>Your session has expired. Please sign in again.</p>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is letting an expired session appear still signed in, or secretly re-signing in with stored credentials, so warn before expiry where possible, show a clear expired message, and guide the visitor to sign in again. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, leave a signed-in session idle, note what happens, and check that the expiry message and sign-in path are clear. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: Why should sessions expire?
Section 29 — Refreshing a session
Refreshing a session means extending or renewing a signed-in state through the server's proper flow, so a visitor who is still active does not have to sign in again unnecessarily. At concept level, the front end notices that a session is near expiry, or that a request was refused because the session expired, and it asks the server, through the approved renewal flow, whether the session can be renewed. The server decides, based on its own rules, such as how long the session has lived and whether it has been revoked, and the front end then either continues or guides the visitor to sign in again. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
The front-end handling matters for usability. A silent renewal that succeeds is fine, because the visitor's work continues uninterrupted, but a failed renewal must not be hidden: the visitor should see a clear message and a sign-in path, rather than a page that silently stops working. The front end must not invent its own renewal by reusing stored credentials, must not store a password to renew automatically, and must not treat a failed renewal as a reason to show private content from a cache as if it were current. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A document editor that renews its session in the background while you type, and only asks you to sign in again if renewal fails, handles refreshing well.
<p>Your session was renewed. You can continue working.</p>
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is reusing stored credentials in front-end code to renew a session automatically, so renew only through the server's approved flow, show a clear message on failure, and guide the visitor to sign in again. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, explain, in plain words, the difference between a successful renewal and a failed renewal, from the visitor's point of view. A good check is specific: it names what should happen, what you observed, and whether the two match. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked.
Check: When should a session be refreshed?
Section 30 — Handling expired sessions in the interface
When a session expires while a visitor is working, the interface faces a difficult moment: the visitor may have unsaved work, may be in the middle of a form, and may not understand why a page that was working has suddenly stopped. A good interface handles this moment with honesty and care, by explaining that the session has ended, by preserving what can be preserved, such as a draft or form contents where that is safe, and by offering a clear path back, usually signing in again and then returning to the task. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for.
Poor handling looks like a blank page, a confusing error, a silent redirect to the home page, or a form that appears to submit successfully while the server actually rejected it. To avoid those outcomes, the front end should detect expired-session responses from the server, show a plain message, avoid losing the visitor's work where it can safely keep it, and after re-sign-in, return the visitor to their task where possible. It should never fake success, never show stale private data as if it were current, and never ask the visitor to paste credentials into an unexpected pop-up that could be mistaken for a phishing attempt. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug.
A form that, on session expiry, saves its draft, shows your session has ended, and after sign-in returns you to the same form, handles expiry with care.
<p>Your session has ended. Sign in again to continue, and your draft has been saved.</p>
Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape. Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone.
A common mistake is faking success, losing work silently, or redirecting to the home page without explanation, so detect expiry, explain it plainly, preserve safe drafts, and return the visitor to their task after sign-in. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form.
To check your own work, trigger an expired session in a test account of your own and check that the message, draft handling and return path are clear. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: How should an interface handle an expired session?
Section 31 — Role-based interface, not role-based security
Roles describe groups of visitors with different permissions, such as reader, contributor, editor and administrator, and the interface often changes by role, showing different menus, buttons and pages to each group. That is good design: a reader does not need to see an editor's tools, and an administrator's settings should not clutter a contributor's screen. The interface change, however, is a matter of presentation, not of security, and it must never be mistaken for enforcement. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for.
The reason is the same as for protected routes: a visitor can bypass the interface and request the underlying data or action directly, so the server must check the visitor's role for every request, regardless of what the interface showed. A front-end developer therefore builds role-based views by asking the server what role the visitor has, and then presenting the appropriate options, while understanding that the server will refuse any action the role does not allow, even if the visitor manages to trigger it. Testing should include trying, in a test account, to reach a higher role's features, to confirm the server refuses them. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug.
An editor's dashboard that shows a publish button, while a reader's view of the same site shows no publish button, is a role-based interface.
<button>Publish article</button>
Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape. Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone.
A common mistake is assuming that hiding an administrator tool from a role means that role cannot use it, so present role-appropriate options, and rely on the server to enforce every role check on every request. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form.
To check your own work, sign in as a lower-role test account and check that higher-role options are both hidden and refused by the server. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: Why is a role-based interface not role-based security?
Section 32 — Loading states during login
Logging in takes a moment, while the browser sends the details to the server and waits for an answer, and during that moment the interface must communicate clearly that something is happening. A loading state, such as a disabled button with the text Signing in, or a short progress message, tells the visitor to wait, prevents them from submitting the form twice, and prevents the confusion of a page that appears to do nothing. Without a loading state, visitors often click again, submit duplicate requests, or leave, believing the page is broken. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent.
Build the loading state by disabling the submit button as soon as the form is submitted, changing its label to show progress, and keeping the form's contents visible so the visitor can see what was submitted. If the login fails, restore the button, show the error clearly, and let the visitor correct and retry without losing their email address, where that is safe. If the login succeeds, move promptly to the signed-in view, rather than leaving the visitor on a loading screen. Keep the loading message honest: do not claim success before the server confirms it. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened.
A sign-in button that changes to Signing in and becomes disabled while waiting, then restores itself with an error if login fails, handles loading well.
<button disabled>Signing in…</button>
After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts. If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved.
A common mistake is leaving the button active during login, so visitors submit the form several times, or claiming success before the server confirms, so disable the button, show progress honestly, and restore it with a clear error if login fails. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix.
To check your own work, submit a login form on a slow connection and check that the button shows progress and cannot be submitted twice. Write down what you checked and what you found, especially when the result surprises you; that record becomes useful evidence later. The goal of checking is confidence you can defend, not a quick glance that lets you move on.
Check: Why does a login form need a loading state?
Section 33 — Accessible login forms
Login forms must work for everyone, including visitors who use screen readers, keyboards, voice control or magnification, because authentication is the gateway to an account, and a login form that excludes someone locks them out entirely. Accessibility in login forms is therefore not a refinement; it is a requirement for the form to do its job. The good news is that accessible login forms are mostly ordinary good forms, built with real labels, clear order and visible focus, rather than with special tricks. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent.
Use a real label for every field, linked to its input, so assistive technology can announce what each field is for. Use the correct input types, such as email and password, and autocomplete values, so browsers and password managers can help fill the form correctly. Keep the tab order logical, make the focused field clearly visible, and show errors in text next to the field they concern, not only by colour, so a visitor who cannot see colour still understands what went wrong. Avoid CAPTCHAs that block visitors where possible, and where a challenge is required, offer an accessible alternative. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened.
A login form with labelled email and password fields, visible focus, and a text error next to the password field is accessible in its basic form.
<label for="email">Email address</label><input id="email" type="email" autocomplete="username">
After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts. If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved.
A common mistake is using placeholder text as the only label, or showing errors only by colour, so label every field, use correct types and autocomplete, keep focus visible, and show errors as text. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix.
To check your own work, navigate a login form by keyboard alone, with a screen reader if you can, and check that every field and error is announced clearly. Write down what you checked and what you found, especially when the result surprises you; that record becomes useful evidence later. The goal of checking is confidence you can defend, not a quick glance that lets you move on.
Check: What makes a login form accessible?
Section 34 — Autofill and password managers
Password managers are tools that generate, store and fill strong, unique passwords for each site, so visitors do not have to remember dozens of passwords or reuse one password everywhere. Browsers have built-in managers, and many people use dedicated ones, and a front-end developer's job is to make login and registration forms work well with them, rather than fighting them. A form that blocks autofill, uses unusual field names, or splits a password across several fields makes password managers fail, and pushes visitors back towards weak, reused passwords. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for.
Support managers by using standard field names and autocomplete values, such as username or email for the identifier and current-password for sign-in and new-password for registration or password change. Keep the sign-in form to one identifier field and one password field, in a logical order, and do not disable pasting into password fields, because pasting is how managers and many visitors enter long, generated passwords. Test the form with a browser's built-in manager, and check that it offers to save a new password after registration and to fill the correct password on return visits. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug.
A registration form that a password manager fills correctly, and then offers to save, is working with the visitor's tools rather than against them.
<input type="password" autocomplete="new-password">
Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape. Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone.
A common mistake is disabling pasting, blocking autofill, or using non-standard field names that confuse managers, so use standard autocomplete values, allow pasting, and test with a browser password manager. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form.
To check your own work, test a registration form with a browser password manager and check that it fills and offers to save the password correctly. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: Why should forms work with password managers?
Section 35 — Passkeys and biometrics in plain words
Passkeys are a newer way to sign in that replaces a typed password with a cryptographic key pair, unlocked on the visitor's device by something they have, such as a phone, or something they are, such as a fingerprint or face. At concept level, the device holds a private key that never leaves it, and the server holds the matching public key, which is not secret in the same way. When the visitor signs in, the device proves it holds the private key, without sending the private key itself, and the visitor confirms with a biometric or a device PIN. The result is a sign-in that is resistant to phishing, because there is no password to steal or to trick a visitor into entering on a fake page. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
For front-end developers, passkeys mostly appear as a browser-provided sign-in option, such as a button that says Sign in with a passkey, which triggers the device's own prompt. The front end should present that option clearly, alongside password sign-in where both are offered, explain in plain words what will happen, and handle the cases where the device has no passkey, where the visitor cancels, or where the browser does not support passkeys, by falling back gracefully to another method. All cryptographic details belong to the browser and the server; the front end must never try to handle, store or display private keys itself. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A sign-in page that offers both a password form and a Sign in with a passkey button, and that falls back to password sign-in if no passkey is available, follows the pattern.
<button>Sign in with a passkey</button>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is trying to handle or store private keys in front-end code, or offering passkeys with no fallback, so present passkeys as a browser-provided option, explain what happens, and fall back gracefully when unavailable. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, explain, in plain words, why a passkey is hard to phish, and what happens if a device has no passkey.
Check: What is a passkey, in plain words?
Section 36 — Device trust in plain words
Device trust is the idea that some devices are more trusted than others, because they are recognised, protected and under the account holder's control, while a new or shared device is less trusted until it proves itself. A site might therefore allow a recognised phone to sign in with fewer steps, while asking a brand-new device for an extra check, such as a code sent to a known email address or a confirmation on a trusted device. The goal is to balance convenience on familiar devices with caution on unfamiliar ones, without locking the genuine owner out of their own account. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for.
In the front end, device trust shows up as messages and choices, such as this device looks new, please confirm it is you, or a list of trusted devices in account settings where a visitor can remove a device they no longer own. The front end should explain why an extra check is being asked for, in plain words, rather than showing a generic error, and it should offer a clear recovery path if the visitor cannot complete the check. It should never claim a device is trusted based only on a front-end check, because device identity, like all authentication decisions, must be decided by the server. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug.
An account settings page that lists trusted devices, with a Remove button for each, lets a visitor manage device trust themselves.
<p>Trusted devices</p><button>Remove this device</button>
Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape. Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone.
A common mistake is trusting a device based only on a front-end check, or giving no way to remove a lost device, so explain extra checks plainly, let visitors manage trusted devices, and leave trust decisions to the server. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form.
To check your own work, find a trusted-devices list in an account you own, and check that you can understand and remove a device from it. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: What is device trust?
Section 37 — Signing out everywhere
Signing out everywhere, sometimes called signing out of all devices, ends a visitor's sessions on every device at once, rather than only on the device they are currently using. It is an important recovery tool: if a phone is lost, if an account is suspected of being misused, or if a visitor realises they signed in on a shared computer and forgot to sign out, signing out everywhere cuts off all active sessions, including the one on the lost or shared device. After such a sign-out, every device must sign in again, which is inconvenient, but that inconvenience is the point: it ensures no forgotten session remains usable. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
In the front end, the option usually lives in account settings, clearly labelled, with a plain explanation of what it will do, and a confirmation step, because it is a destructive action that will sign the visitor out of their current device too. After confirmation, the interface should show a clear success message, return the visitor to a signed-out view, and invite them to sign in again and, where appropriate, to change their password and review recent activity. The front end must not pretend to sign out everywhere while only signing out locally; the server must end every session. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
An account settings page with a clearly explained Sign out of all devices button, a confirmation step, and a signed-out message afterwards, follows the pattern.
<button>Sign out of all devices</button><p>This will sign you out everywhere, including this device.</p>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is pretending to sign out everywhere while only signing out locally, so label the option clearly, confirm before acting, let the server end every session, and show a clear result. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, explain, in plain words, a situation where signing out of all devices is the right response, and what happens afterwards. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: When would you sign out everywhere?
Section 38 — Auth state in app architecture
Auth state is the application's overall record of whether the visitor is signed in, who they are, and what role they have, and in a larger application it is shared across many screens, rather than being checked separately on each page. At concept level, the application asks the server once, or on a regular cycle, for the current auth state, stores that answer in a central place for the interface to read, and updates every screen that depends on it when the answer changes, such as after sign-in, sign-out or expiry. That central approach keeps the interface consistent, so a header, a dashboard and a settings page do not disagree about whether the visitor is signed in. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
The front-end architecture lesson is to treat auth state as a single source of truth for display, while remembering that it is still only display. The server remains the authority for every private request, and the front end must re-check with the server when it matters, rather than trusting a cached auth state forever. When auth state changes, the interface should update promptly, clear private data from the previous visitor's view, and avoid showing a mixture of two visitors' content, which is a common fault when auth state is managed carelessly. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
An app whose header, dashboard and settings all switch together from signed-out to signed-in, without disagreeing, has a central auth state.
<p>Welcome back, Ada.</p>
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is keeping separate, disagreeing copies of auth state on different screens, or trusting a cached state forever, so keep one central auth state for display, re-check with the server when it matters, and clear private data on change. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, sign in and out of an app and check that every screen updates together, with no leftover private content from the previous state. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: What is auth state?
Section 39 — Network failures during login
Login can fail for reasons that have nothing to do with the password, such as a lost connection, a slow network, or a server that is temporarily unavailable, and the interface must distinguish those failures from a wrong password, or at least handle them honestly. A visitor who sees email or password incorrect when the real problem is a lost connection may try a different password, lock themselves out, or conclude their account is broken, when the correct action is simply to wait and try again. Honest error handling therefore matters as much for usability as for safety. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
The front-end pattern is to show a clear, specific message for a network failure, such as unable to reach the server, please check your connection and try again, and to keep the form's contents so the visitor can retry without retyping everything. Do not show a password error for a network error, do not claim the login succeeded when it did not, and do not automatically retry with the password in a loop, which could lock the account. Offer a retry button, and let the visitor decide when to try again, rather than submitting repeatedly without their knowledge. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A login form that, on a lost connection, shows unable to reach the server and a Retry button, while keeping the email address filled in, handles network failure well.
<p>Unable to reach the server. Please check your connection and try again.</p><button>Retry</button>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is showing a password error for a network failure, or retrying automatically in a loop, so distinguish network errors from credential errors, keep form contents for retry, and let the visitor choose when to retry. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, turn off a connection, attempt a login, and check that the message describes the connection, not the password. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: Why distinguish a network failure from a wrong password?
Section 40 — Honest security interfaces
An honest security interface tells the visitor the truth about their account's safety, without exaggerating, inventing or hiding. It does not claim an account is secure when it is not, does not show a padlock or a shield merely as decoration, and does not use vague phrases such as fully protected to imply a guarantee that no system can honestly give. Visitors make decisions based on these signals, such as whether to store sensitive information or to trust a site with payment details, so a misleading security signal can cause real harm, even if it was intended only as reassurance. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer.
Build security interfaces by stating facts, not promises: whether two-factor authentication is on or off, when the password was last changed, which devices are trusted, and whether a session is current. Use familiar, accurate symbols, such as the browser's own padlock for a secure connection, rather than inventing your own security badges. When something is not protected, say so plainly and offer a next step, such as enabling two-factor authentication, rather than hiding the gap behind reassuring design. Honesty here builds the trust that decoration cannot. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system.
An account security page that lists, factually, whether two-factor authentication is on, when the password last changed, and which devices are trusted, is honest.
<p>Two-factor authentication: On</p><p>Password last changed: 12 March</p>
The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content. After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts.
A common mistake is inventing security badges, or claiming an account is fully protected, so state security facts plainly, use only genuine signals, and offer a next step where protection is missing. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first.
To check your own work, review an account security page and check that every claim on it is a fact you could verify, not a vague promise. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked. Write down what you checked and what you found, especially when the result surprises you; that record becomes useful evidence later.
Check: What makes a security interface honest?
Section 41 — Testing login flows safely
Testing a login flow means trying it out to find faults before real visitors meet them, and it must be done safely, using accounts and systems you own or have permission to test. Create a test account of your own, try signing in with the correct details, with wrong details, with an empty form, with a slow connection, and after signing out, and note what the interface shows at each step. Testing your own system in this way is legitimate development work, and it is how you discover confusing errors, broken redirects and lost drafts before they affect anyone else. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent.
What you must not do is test a login flow against a system you do not own or do not have permission to test, try to guess another person's password, or use a test to access someone else's account, even if you believe you are only checking. Unauthorised access is a crime, including under Nigeria's Cybercrimes Act, and a test that accesses another person's account is not a test at all. Keep test accounts separate from real accounts, delete test data when you are finished, and never use a real visitor's credentials for testing, even with good intentions. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened.
A developer who creates a test account, tries correct, wrong and empty logins, and records the messages at each step, is testing safely.
<p>Test account: test@example.com (own account, for testing only)</p>
After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts. If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved.
A common mistake is testing login flows against accounts or systems you do not own or have no permission to test, so test only your own accounts and systems, keep test data separate, and delete it when finished. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix.
To check your own work, plan a login test using only an account you own, list the cases you will try, and note the result of each. Write down what you checked and what you found, especially when the result surprises you; that record becomes useful evidence later. The goal of checking is confidence you can defend, not a quick glance that lets you move on.
Check: What is safe login testing?
Section 42 — Privacy in login interfaces
Login interfaces handle personal data, including email addresses, names and sometimes phone numbers, and they should display as little of that data as necessary, especially on screens that might be seen by others. A login screen that pre-fills a full email address on a shared computer, a welcome message that displays a full name on a public screen, or an error that reveals part of an account's details, all leak personal information unnecessarily. Privacy in login design is therefore about restraint: show what the visitor needs to complete the task, and nothing more. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
Apply that restraint by avoiding pre-filled personal data on shared devices, by showing only the minimum needed to confirm identity, such as a first name rather than a full email address where possible, and by never displaying passwords, codes, tokens or full account identifiers in the interface. Avoid logging personal data or credentials in browser logs, error reports or analytics, because logs are often shared more widely than intended. When a visitor's data must be shown, such as in account settings, keep it behind the signed-in view, and offer a way to hide or mask sensitive values. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A sign-in screen that asks for an email address without pre-filling it on a shared device, and that never displays a password or code, follows the privacy pattern.
<label for="email">Email address</label><input id="email" type="email">
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is pre-filling personal data on shared devices, or displaying passwords, codes or full identifiers, so show only what is needed, mask sensitive values, and never log credentials or personal data. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, review a login and account screen and list every piece of personal data it displays, then ask whether each one is necessary. A good check is specific: it names what should happen, what you observed, and whether the two match. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked.
Check: What personal data should a login screen show?
Section 43 — Third-party login buttons honestly
Third-party login buttons, such as Continue with Google or Continue with Apple, are shortcuts that use another provider's account to sign a visitor in, and they must be presented honestly, without exaggeration or imitation. A button should name the provider exactly, use the provider's recognised style where required, and lead to the provider's own sign-in, as covered in the social login section. It should not claim an endorsement the provider has not given, should not look like the provider's own page, and should not collect the visitor's provider password on your site. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for.
The front-end placement also matters. Offer third-party buttons as alternatives, not as the only way in, unless the site genuinely has no other sign-in method, because visitors without that provider's account, or who do not wish to link it, still need a way to use the site. Explain, in plain words, what information the site will receive from the provider, such as a name and email address, and what it will not receive, such as the provider password. After sign-in, treat the account like any other account, with its own settings, sign-out and recovery options. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug.
A sign-in page that offers Continue with Google alongside an email sign-in form, with a short note about what information will be shared, presents the option honestly.
<button>Continue with Google</button><p>We will receive your name and email address.</p>
Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape. Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone.
A common mistake is imitating a provider's sign-in page, or making a third-party button the only way in without explanation, so name the provider, lead to its own sign-in, explain what is shared, and offer an alternative sign-in method. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form.
To check your own work, review a sign-in page's third-party buttons and check that each names its provider, explains what is shared, and is not the only option. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: How should a third-party login button be presented?
Section 44 — Account deletion and deactivation
Account deletion and deactivation are different actions, and the interface must distinguish them clearly, because visitors often choose one when they meant the other. Deactivation temporarily suspends an account, often with the possibility of restoring it later, while deletion permanently removes the account and, usually, its data, subject to the site's retention rules. A visitor who wants a break but chooses deletion by mistake may lose content they intended to keep, and a visitor who wants their data removed but chooses deactivation may believe their data is gone when it is not. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
The front-end pattern is to offer both options separately, explain each in plain words, including what will happen to content and data, and require a confirmation step for deletion, such as typing a confirmation or re-entering a password through the proper flow. After deletion, show a clear confirmation and return the visitor to a signed-out view, and do not leave private content visible. Offer a way to download personal data before deletion where the site provides it, and explain any waiting period honestly, rather than claiming immediate deletion when data is actually retained for a stated period. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
An account settings page that offers Deactivate account and Delete account as separate options, each with a plain explanation and a confirmation step, follows the pattern.
<button>Deactivate account</button><button>Delete account</button><p>Deletion permanently removes your account and content.</p>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is presenting deletion and deactivation as if they were the same, or deleting without confirmation, so explain each option plainly, confirm deletion separately, and show a clear result afterwards. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, find an account settings page and check whether deletion and deactivation are explained separately, with confirmation for deletion. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: What is the difference between deletion and deactivation?
Section 45 — Changing a password
Changing a password is a routine account action, but it must be handled carefully, because it is also the action an attacker would take after gaining access to an account. The front-end flow usually asks for the current password, then a new password twice, and then confirms the change, while the server checks the current password, enforces its password rules, and ends other sessions where appropriate. The interface should guide the visitor through those steps without revealing anything about the old password and without storing any password in front-end code or browser storage. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
Good password-change forms use password fields with a show toggle, support password managers with the correct autocomplete values, and give clear, specific feedback about the new password's requirements, such as length or character rules, without being so strict that visitors resort to predictable passwords. After a successful change, the interface should confirm the change, show that the visitor remains signed in on the current device where appropriate, and advise them to check for unexpected activity if they did not make the change themselves. If the visitor cannot remember the current password, the form should direct them to the password reset flow, rather than offering a shortcut around it. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A password-change form that asks for the current password, a new password twice, and confirms the change afterwards, follows the pattern.
<label for="new-password">New password</label><input id="new-password" type="password" autocomplete="new-password">
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is allowing a password change without checking the current password, or storing passwords in front-end code, so require the current password, support password managers, confirm the change, and direct forgotten passwords to reset. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, review a password-change form and check that it asks for the current password, uses password fields, and confirms the result. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: What should a password-change form ask for?
Section 46 — Changing an email address
Changing the email address associated with an account is a sensitive action, because the email address is often used for password resets, notifications and account recovery, so changing it to an attacker's address could hand over the account. The front-end flow therefore usually asks the visitor to enter the new address, confirm it, and then verify it, by following a link or entering a code sent to the new address, before the change takes effect. Until verification is complete, the old address should remain the account's trusted address, and the visitor should be told plainly what is happening. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for.
The interface should explain each step, show which address is currently verified, and warn the visitor that a change is in progress, so they are not surprised if a reset link goes to the old or new address during the transition. It should also notify the old address that a change was requested, where the system supports it, so the genuine owner can act if the change was not theirs. As with other sensitive changes, the front end must not bypass verification for convenience, and must not display verification links or codes in the interface. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug.
An account settings page that shows the current verified email, asks for a new address, and explains that the new address must be verified before it takes effect, follows the pattern.
<p>Current email: ada@example.com (verified)</p><label for="new-email">New email address</label><input id="new-email" type="email">
Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape. Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone.
A common mistake is changing an account's email address without verifying the new address, so require verification of the new address, keep the old address trusted until then, and notify the old address where supported. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form.
To check your own work, explain, in plain words, what an attacker could do if they could change an account's email address without verification. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: Why must a new email address be verified?
Section 47 — Recovery codes
Recovery codes are a set of one-time codes given to a visitor when they set up two-factor authentication, to be used if they lose access to their usual second factor, such as losing a phone that generates codes. Each code can typically be used once, and the set should be stored by the visitor in a safe place, such as a password manager or printed and kept securely, because anyone who obtains the codes can bypass the second factor. Recovery codes are therefore a backup key, and they must be treated with the same care as a password. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
In the front end, recovery codes appear at setup, with a clear explanation of what they are, how to store them, and how to use one if the usual method is unavailable. The interface should let the visitor copy or download the codes, confirm they have saved them, and offer to generate a new set, which invalidates the old set, if the codes are suspected of being compromised. The front end must never store recovery codes in browser storage, must never display them again casually after setup, and must never include them in front-end code, logs or screenshots. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A two-factor setup screen that shows ten recovery codes once, explains to store them safely, and asks the visitor to confirm they have saved them, follows the pattern.
<p>Save these recovery codes somewhere safe. Each code can be used once.</p>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is storing recovery codes in browser storage, or displaying them again casually after setup, so show recovery codes once at setup, explain safe storage, confirm they were saved, and allow regeneration if compromised. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, explain, in plain words, when you would use a recovery code, and why it must be stored safely. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: What are recovery codes for?
Section 48 — Authentication in single-page apps
In a single-page application, the page does not reload for each view, so authentication state must be managed as the visitor navigates between views without a full page refresh. At concept level, the application still relies on the server to decide who is signed in and what they may do, and it still presents protected views only after the server confirms the visitor's state. The difference is in how the interface reacts: instead of the server sending a whole new page for each view, the application updates its own views in response to the server's answers, while keeping the session handling on the server side. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
The front-end lessons from earlier sections apply with extra care here, because it is tempting, in a single-page application, to manage tokens and auth state entirely in front-end code. Resist that temptation: keep credentials server-managed, do not store tokens in browser storage, and treat the application's auth state as display only, re-checking with the server for private data on every request. Handle navigation honestly, so a signed-out visitor who tries to open a protected view is guided to sign in, and a signed-in visitor whose session expires is told plainly and guided back, rather than shown stale private content from a previous view. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A single-page app that, on opening a protected view while signed out, shows the sign-in view instead, and after sign-in returns to the requested view, follows the pattern.
<p>Please sign in to view your dashboard.</p>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is managing tokens entirely in front-end code, or showing stale private content after sign-out, so keep credentials server-managed, treat app auth state as display only, and guide expired or signed-out visitors to sign in. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, navigate a single-page app while signed out, and check that protected views guide you to sign in rather than showing private content. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: How is authentication different in a single-page app?
Section 49 — Auth and lockout recovery
Lockout recovery is the process of regaining access when a visitor cannot sign in, whether because they forgot a password, lost a second factor, or were locked out after too many attempts. It is a moment of stress, because the visitor is locked out of something they own, and a poor recovery experience can push them towards unsafe shortcuts, such as sharing credentials or creating a duplicate account with a weak password. A good recovery experience therefore offers clear, legitimate paths, explains each step, and sets honest expectations about time and verification. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks.
The front-end recovery options usually include password reset through a verified email address, recovery codes for two-factor loss, and, where the site provides it, a support or identity-verification process for cases where neither works. The interface should present those options in order, from the simplest to the most involved, explain what each requires, and avoid promising immediate recovery where verification will take time. It must not offer a shortcut that bypasses authentication, such as revealing a password or skipping a second factor on request, because that shortcut would be available to an attacker as well as to the genuine owner. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once.
A locked-out visitor who sees, in order, reset password, use a recovery code, and contact support, with a plain explanation of each, has a clear recovery path.
<p>Locked out? Reset your password, use a recovery code, or contact support.</p>
If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved. Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape.
A common mistake is offering a shortcut that bypasses authentication, or promising immediate recovery where verification takes time, so offer legitimate recovery paths in order, explain what each requires, and set honest expectations. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult.
To check your own work, list the recovery options a site offers, in order, and check that none of them bypasses authentication. The goal of checking is confidence you can defend, not a quick glance that lets you move on. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea.
Check: What should a locked-out visitor be offered?
Section 50 — Putting it together: an authentication checklist
An authentication checklist turns this chapter into a habit, so no login, logout or account feature goes live with a missing protection or a misleading interface. The checklist is not a guarantee of security; security is a property of the whole system, including the server, and no front-end checklist alone can provide it. What the checklist can do is catch the front-end mistakes that are most common and most costly, such as a secret in code, a token in browser storage, a missing logout, or an error message that leaks information, before visitors meet them. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
Before publishing an authentication feature, confirm that no secret appears in front-end code, that no token or password is stored in browser storage, that login and logout flows work end to end, that errors are general rather than leaking, that forms are labelled and keyboard-usable, that HTTPS is used throughout, that expired sessions are handled clearly, and that recovery paths, including password reset and, where relevant, recovery codes, are present and explained. After publishing, test the feature as a visitor, on a phone and with a keyboard, and fix anything the test reveals. Keep the checklist short enough to use every time, and update it when you learn a new lesson, so it grows with your experience. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A checklist for a login feature might fit on one page, with tick boxes for secrets, storage, logout, errors, accessibility, HTTPS, expiry and recovery.
<p>Checklist: no secrets in code, no credentials in browser storage, logout works, errors are general.</p>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is publishing an authentication feature without checking secrets, storage, logout, errors or recovery, so use a short, written checklist before every authentication launch, and test the feature as a visitor afterwards. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, write an authentication checklist from this chapter, then use it on a real login feature and note anything it catches. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: What should an authentication checklist include?
Practical task
Build a login, logout and account-settings prototype for a fictional site, using only test accounts you own: a labelled, keyboard-usable sign-in form with a show-password toggle, a general error message for failed logins, a loading state, a clear sign-out button, a signed-in view and a signed-out view, a password-change form, and a password-reset request screen with a neutral confirmation. Do not put any real password, token or key in the code, do not store any credential in browser storage, and write a short note explaining, for each feature, what the front end presents and what the server must decide.
Chapter questions
- Explain, in plain words, why a secret written into front-end code is not hidden, even if the code is minified.
- Why is browser storage not a safe place for a login token, even though it is convenient?
- A visitor is signed out, but a protected page's button is hidden rather than enforced. Explain what could still go wrong, and what the server must do.
- Design the error messages for a failed login, a password reset request and an account lockout, so that none of them leaks whether an account exists.
- A team proposes storing a token in browser storage to make a single-page app simpler. Explain, in plain words, why you would refuse, and what server-managed alternative you would propose instead.