InstagramEmail and password login

100%

Email and password login.

A returning user enters their email or username and password, gets a session, and lands on the page they were trying to reach.

Beginner19 minUpdated 2 Oct 2026

Requirements exploration.

This design covers the web login screen for a photo-sharing product. The server is treated as a black box, and components are described by what they're responsible for, not by library.

Clarifying questions

Q1
Use casesWhat are the main use cases?
A returning user logs in with an identifier and password. Everything else on the screen is a link to another flow.
Q2
IdentifiersWhich identifiers are accepted?
Email or username in one field. Phone login is covered by the sibling Phone number and OTP page.
Q3
ScopeWhat happens after the password step?
The server may return a second-factor challenge or a checkpoint. This page hands off to those flows; it does not implement them.
Q4
SecurityWhat are the security constraints?
Credentials are never stored or logged on the client. The session must not be readable by JavaScript.

Functional requirements

#1User can enter an email or username and a password.
#2User can submit with the button or the Enter key.
#3User sees a clear message when credentials are wrong, the account is locked, they are rate limited, or the network fails.
#4On success, user is redirected to the page they originally requested, or to Home.
#5If a second factor or checkpoint is required, user is taken to that flow with the challenge context.
#6User can reach Forgot password and Sign up from this page.
#7A user who already has a valid session is redirected away from this page.

Non-functional requirements

No.AreaRequirementTarget / measure
01PerformanceThe form is visible and usable almost immediately, even on slow networks.
LCP < 1.5sInteractive < 2sRoute JS < 30 KB
Mid-range phone on 4G, compressed JS.
02SecurityNo credential exposure, CSRF-safe, session in an HttpOnly cookie.
0 credentials in URLs, logs, analytics or storage
03User experienceClear loading, error and success states. No double submits.
Every error has text + a recovery action
04AccessibilityFully operable by keyboard and screen reader.
WCAG 2.1 AA
05Password managersBrowser and third-party managers fill and save credentials.
Chrome · Safari · Firefox built-insMajor managers
06InternationalisationAll strings translated; right-to-left layouts supported.
All supported locales

Scope

In scope
Login route and form
Client validation and error display
Login API contract
Session handling on the client
Redirect after login
Handoff to challenge flows
Out of scope
Phone and OTP loginsibling page
Two-factor and checkpoint screenschallenge flow
Sign up and Forgot passwordown pages
Social loginseparate design
Server-side fraud detectionserver team
Was this section helpful?

High level design.

Only the Auth Service knows about HTTP and CSRF. The form knows nothing about the network, and the store is the single place other surfaces read login state from.

Component diagram

Click a card to see its block and connections in the diagram.

Component diagram. The numbered component cards that follow describe each part.
Component diagramComponents: 1. LoginPage (Route shell. Reads next, renders the form and links, reacts to store changes.), 2. LoginForm (Holds field values and validation, dispatches login, moves focus after errors. Includes the four fields and the live error banner.), 3. Auth Store (App-wide auth state: who is signed in and any pending challenge. Never holds the password.), 4. Auth Service (The only module that calls auth endpoints. Adds CSRF, times out, maps responses to typed results.), 5. Session Guard (Protects routes, validates next, sends signed-in users away and routes challenges.), 6. Server (Checks credentials, rate limits, decides challenges, sets the session cookie.).

Browser

login(credentials)

result or typed error

status changes

POST /api/auth/login
GET /api/auth/csrf

200 + Set-Cookie
HttpOnly · Secure · Lax

loginRequested

status · error · challenge

1LoginPage/accounts/login?next=/some/path

2LoginForm
IdentifierFieldemail or usernamePasswordField+ ShowHideToggleSubmitButtonidle / submittingFormErrorBanneraria-live region

ForgotPasswordLink · SignUpLink · AlternateLoginLinks

3Auth Store
{ status, viewer, error, challenge }

4Auth Service
CSRF · timeout · abort
error mapping · no POST retries

5Session Guard / Router
logged in → leave /accounts/login
success → safe next or Home
challenge → 2FA or checkpoint

6Server
black box · Auth API
rate limiting · risk checks

Sequence diagram

The happy path, from pressing Enter to landing on the next page. Scroll sideways to follow it.

Sequence diagram, as an ordered list of steps:
Sequence diagram12 steps between User, LoginForm, Auth Store, Auth Service, Server, Router. The steps are listed as text after the diagram.RouterServerAuth ServiceAuth StoreLoginFormUserstatus = submittingverify, risk checkstatus = authenticatedtype + Enter1validate locally → ok2loginRequested3login(creds)4POST /api/auth/login + X-CSRF-Token5200 + Set-Cookie6Success(viewer)7status changed8redirect to next or Home9
  1. User → LoginForm: type + Enter
  2. LoginForm → LoginForm: validate locally → ok
  3. LoginForm → Auth Store: loginRequested
  4. Note over Auth Store: status = submitting
  5. Auth Store → Auth Service: login(creds)
  6. Auth Service → Server: POST /api/auth/login + X-CSRF-Token
  7. Note over Server: verify, risk check
  8. Server → Auth Service (reply): 200 + Set-Cookie
  9. Auth Service → Auth Store (reply): Success(viewer)
  10. Note over Auth Store: status = authenticated
  11. Auth Store → Router: status changed
  12. Router → User (reply): redirect to next or Home
When the server says noAuth Service maps each response; the tag shows who reacts.
CodeOutcomeKindWhat happensReacts
401invalid_credentialserrorBanner, focus password, keep identifier.2LoginForm
200two_factor_requiredchallengeRoute to /accounts/login/two_factor.5Session Guard
403checkpoint_requiredchallengeRoute to /challenge.5Session Guard
429rate_limitederrorBanner with wait time, submit disabled.2LoginForm
ERRnetwork error / timeouterror“Check your connection”, values kept.2LoginForm
Was this section helpful?

Data model.

The password lives in exactly one place, and only briefly. Everything else the app needs after login fits in a small store.

Read left to right: the password dies at step 02. Solid arrows: field of that type. Dashed: the server response fills 03 and 04.keywordmodelfieldprimitivevalue
// 01 · LoginForm
// Component memory only.
type LoginFormState = {
errors: Map<Field, string>
identifier: string
isPasswordVisible: bool
password: string
touched: Map<Field, bool>
}
↓ on submit, copy out
// 02 · Auth Service
// Lives only for the request.
type Credentials = {
identifier: string
password: string
}
// 03 · Auth Store
// Read by route guards + header.
type AuthState = {
challenge?: Challenge
error?: AuthError
status: Status
viewer?: Viewer
}
type Status =
| "authenticated" | "challenge"
| "error" | "idle"
| "submitting"
// 04 · Cookie jar
// Set by server. JS cannot read.
cookie Session = {
csrftoken
sessionid // HttpOnly
}
// 03 · Auth Store, nested
// Client maps code to message.
type AuthError = {
code: ErrorCode
message: string
retry_after_s?: int
}
// Used once, then dropped.
type Challenge = {
challenge_id: string
methods: Method[]
type: string
}
// Lets Home render instantly.
type Viewer = {
full_name: string
id: string
profile_pic_url: url
username: string
}
Was this section helpful?

Interface definition.

Login is a single request and response, and nothing needs to be pushed. Plain REST keeps the route bundle small.

Transport choice

Used:HTTP / RESTLogin is one request and one response. Nothing needs to be pushed.
Not used:WebSocket · SSE · long pollingNo real-time need on this screen.
Not used:GraphQLOne fixed-shape mutation. REST is simpler and keeps the route bundle small.
Not used:WebRTCNot applicable.

Server to client APIs

1GET/api/auth/csrf

Sets the csrftoken cookie and returns the token. Called once on page load, or embedded in the server-rendered HTML to save a round trip.

Request
No body, no headers.
Response200
{ "csrf_token": "b1f3...e9" }
2POST/api/auth/login

Verifies credentials and, on success, sets the session cookie.

Request
Headers
Content-Type: application/jsonX-CSRF-Token: <token>
Credentials
same-origin cookies
{
"identifier": "jane@example.com",
"password": "********",
"device_id": "web-7f2c...",
"next": "/explore/"
}
Response
{
"status": "authenticated",
"viewer": {
"id": "123",
"username": "jane",
"full_name": "Jane Doe",
"profile_pic_url": "https://cdn.example.com/p/123.jpg"
},
"redirect_to": "/explore/"
}
Also setsSet-Cookie: sessionid=…; HttpOnly; Secure; SameSite=Lax

Error cases

Every error comes back in one shape. Auth Service reads the code and the client reacts.

{ "error": { "code": "invalid_credentials", "message": "Your email, username or password is incorrect." } }
HTTPTypeBody codeClient behaviour
400error
invalid_request
Show field errors from the server. Should not happen if client validation matches.
401error
invalid_credentials
Banner: “Your email, username or password is incorrect.” Keep identifier, clear password, focus password.
403challenge
checkpoint_required
Store the challenge, route to the checkpoint flow.
403retry
csrf_failed
Refresh the CSRF token once, then retry the request once automatically.
423error
account_disabled
Banner with a link to the help centre.
429error
rate_limited+ retry_after_s
Banner: “Please wait a few minutes before you try again.” Disable submit with a countdown.
5xxnetwork
timeout · offline
Banner: “We couldn’t connect. Check your connection and try again.” Keep both values.

Client to client communication

Fields talk to LoginForm through props and callbacks. LoginForm and the rest of the app talk through Auth Store actions. Pick a scenario and step through it to see which action fires and what it changes.

Step 1 of 3
Jane types her email and password. Fields report changes to LoginForm through callbacks; nothing reaches the store yet.
example.com/accounts/login?next=/explore/
Log in
Email or username
jane@example.com
Password
••••••••Show
Log in
Actions dispatched
Nothing yet. The user is still typing, so nothing has reached the store.
Auth Store
statusanonymous
errornull
viewernull
challengenull
All actions
auth/loginRequestedLoginForm, on valid submit{ identifier, password, next }
auth/loginSucceededAuth Service{ viewer, redirectTo }
auth/challengeRequiredAuth Service{ challenge }
auth/loginFailedAuth Service{ error: AuthError }
auth/errorDismissedLoginForm, on next editnone
auth/sessionRestoredApp boot{ viewer }

UI component API

LoginForm is reused by the login page and by the login modal that appears when a logged-out user tries to like or follow. Click a prop to see what it controls.

<LoginForm
/>
Prop
defaultIdentifier
Default
""
Why
Prefills the field, for example after sign-up or logout.
Preview
Log in
Email or username
jane@example.com
Password
Show
Log in
Prefilled after logout, so Jane only types her password.
Child components
IdentifierField
valueonChangeonBlurerrorlabel="Email or username"autoComplete="username"autoCapitalize="none"spellCheck={false}inputMode="email"
No autocorrect or capitals, so usernames are not mangled, and the email keyboard on mobile.
PasswordField
valueonChangeonBlurerrorlabelautoComplete="current-password"
Keeps its own isVisible flag, toggled by a button with aria-pressed and the label “Show password” / “Hide password”.
Was this section helpful?

Optimizations.

Not everything can be optimised at once. A quick rubric picks the areas that define this surface, then each one gets a deep dive.

Priority rubric

Each candidate area is scored 1–3 on three questions. The top three become deep dives; the rest are handled elsewhere or do not apply.

RankAreaImpact if it failsUsers affectedHow often it mattersScoreDecision
1Security of credentials and session3 of 33 of 33 of 39Deep diveA leak compromises the account, and every other surface depends on this session.
2Form UX and accessibility2 of 33 of 33 of 38Deep diveEvery logged-out user passes through this form. Friction or confusing errors lose users.
3First-load performance2 of 33 of 32 of 37Deep diveOften the first page a user sees, frequently on a mobile network.
4Internationalisation1 of 32 of 31 of 34SkippedCopy is short and handled by the app-wide i18n layer.
5SEO1 of 31 of 31 of 33SkippedLogin pages should not be indexed.
6Offline support1 of 31 of 31 of 33SkippedLogging in needs the network by definition.
1

Deep dive: security

Every item pairs a concrete attack with the layer that stops it.

No.ConcernRiskApproachOwned by
01Session storageAn injected script steals the session.HttpOnly, Secure, SameSite=Lax cookie set by the server. JavaScript never sees it.6Server
02CSRFAnother site submits the form as the user.Double-submit token from a cookie, sent back in X-CSRF-Token. SameSite=Lax adds a second layer.4Auth Service6Server
03Credentials in URLsThe password ends up in history, logs or referrers.Always POST, including the no-JavaScript fallback.2LoginForm
04Credentials in telemetryThe password leaks into error reports or session replays.Strip request bodies from errors, log only outcome codes, mask password inputs.4Auth Service
05Password in memoryThe password lingers after it is needed.Held only in form state. Cleared after a failure and on unmount, never stored.2LoginForm
06Open redirectnext sends the user to a phishing site.Accept next only as a same-origin path starting with a single /. Anything else goes Home.5Session Guard
07Brute forceAttackers guess passwords at scale.The server rate limits and may challenge. The client honours retry_after_s with a countdown.6Server2LoginForm
08ClickjackingThe login page is framed to trick clicks.The server sends headers that forbid framing.6Server
09Content Security PolicyAn injected script reads typed passwords.A strict policy on this route, with no third-party scripts.6Server
2

Deep dive: form UX and accessibility

Small details decide whether people get in on the first try, including with a screen reader or password manager.

No.ConcernRiskApproachOwned by
01LabelsPlaceholder-only fields lose context once typed.A real <label> per input. A floating label must stay visible after typing.2LoginForm
02Password managersAutofill breaks and users give up.Correct autocomplete values, a real <form>, stable name attributes, no paste blocking. Read values with FormData at submit.2LoginForm
03Validation timingErrors appear while the user is still typing.Validate on blur and on submit. Once a field shows an error, re-validate on change.2LoginForm
04Error presentationScreen reader users miss what went wrong.aria-describedby and aria-invalid on fields, server errors in a role="alert" banner, focus the first invalid field.2LoginForm
3

Deep dive: first-load performance

The page has to be usable fast on a mid-range phone, before most of the app has loaded.

LCP< 1.5sInteractive< 2sRoute JS≤ 30 KB
No.ConcernRiskApproachOwned by
01Server-render the formA blank screen until JavaScript loads.Static HTML with a real form that works before hydration.1LoginPage
02Route bundle budgetHeavy JavaScript delays the first tap on mobile.Keep route JS under 30 KB compressed. Load the challenge flow lazily.1LoginPage
03CSRF token in the HTMLAn extra round trip before the first submit.Embed the token in the server-rendered page instead of calling GET /api/auth/csrf.6Server4Auth Service
04Fonts and imagesLayout shifts and a slow LCP.A system font stack or a preloaded subset, and no hero image on this route.1LoginPage
Was this section helpful?

Trade-offs.

Each decision lists the options that were on the table. The chosen one is first; the others stay visible so the reasoning can be checked.

01
Where the session lives
Chosen:HttpOnly cookie
  • Pro:Not readable by scripts
  • Pro:Sent automatically
  • Pro:Works with server rendering
Downside we accept:
  • Con:Needs CSRF protection
Ruled out:Token in localStorage

Any injected script can steal it; Persists indefinitely

Ruled out:Short token in memory plus refresh cookie

More moving parts; Lost on every reload, costing a refresh call

02
When to validate
Chosen:On blur and submit, then live after the first error
  • Pro:No noise while typing
  • Pro:Fast feedback once a mistake is known
Downside we accept:
  • Con:Slightly more logic
Ruled out:On every keystroke

Shows errors before the user has finished typing

Ruled out:Only on submit

Late feedback

03
Submit button when fields are empty
Chosen:Always enabled, show errors on press
  • Pro:Discoverable by screen readers
  • Pro:Explains what is missing
Downside we accept:
  • Con:An extra press to learn the rule
Ruled out:Disabled until fields look valid

Skipped by keyboard and gives no reason; Autofill without input events can leave it wrongly disabled

04
Wrong-credentials message
Chosen:Generic: identifier or password is incorrect
  • Pro:Does not reveal whether an account exists
Downside we accept:
  • Con:Less specific help
Ruled out:Specific: no such user vs wrong password

Lets attackers enumerate accounts

05
Rendering
Chosen:Server-rendered form, enhanced with JavaScript
  • Pro:Fastest paint
  • Pro:Works without JavaScript
Downside we accept:
  • Con:Needs a server rendering path for this route
Ruled out:Client-rendered route

Blank until JavaScript loads; Slower first paint on mobile

Was this section helpful?
Builds on this
Phone number and OTP login
Read next