After a successful login, a browser should not have to send the password with every request. The application verifies the password once, then gives the browser a session identifier or another credential to present later. How that credential is stored and checked affects both the sign-in experience and what happens if someone steals it.
Authentication establishes which account is making a request. Authorization decides what that account can do. A signed-in user may be authenticated but still have no right to view another user’s invoice. Both checks belong on the server; hiding a button in the browser does not enforce permission.
Passwords: a common starting point
In password-based login, the server looks up an account and checks the submitted password against a stored password hash. It should never need to recover the original password. Use a maintained password-hashing implementation such as Argon2id, scrypt, or bcrypt with appropriate settings, not a fast general-purpose hash. The library handles the per-password salt; review its settings as hardware and deployment needs change.
The browser must send credentials over HTTPS. Login endpoints also need rate limiting and monitoring to deter automated guessing, designed so an attacker cannot easily lock out other users. Error messages should avoid revealing whether an account exists. Treat password reset as part of the same security boundary: a strong login form cannot make up for predictable reset tokens or tokens that never expire.
Passwords work across devices without specialized hardware, but users can reuse them, disclose them, or enter them on deceptive sites. Additional factors and passkeys address some of those risks. Neither removes the need for secure account recovery.

How the application stays signed in
Server-side sessions
With a server-side session, the application creates an unpredictable session ID after login and stores the account and session state on the server. The browser receives the ID in a cookie and sends it with later requests. That cookie holds a reference, not the session record. Revocation is relatively straightforward: invalidate the server-side record, and the ID no longer authenticates requests.
For a typical browser application, mark the session cookie Secure so it travels only over HTTPS and HttpOnly so JavaScript cannot read it. Choose an appropriate SameSite setting, and narrowly scope the cookie’s domain and path. Those settings reduce risk but do not replace cross-site request forgery (CSRF) defenses. Browsers attach cookies automatically, so state-changing requests may need CSRF tokens and origin checks, especially if cross-site requests are permitted. Rotate the session ID at login to prevent session fixation, and invalidate sessions on logout according to the product’s policy.
Bearer tokens
A bearer token authorizes whoever presents it. An API might issue a short-lived access token that a client sends in an Authorization header. Some systems also issue a longer-lived refresh token to obtain new access tokens. This can suit mobile clients or APIs with several client types, but it introduces decisions about storage, rotation, expiration, and revocation.
A JSON Web Token (JWT) is one possible token format, not another word for authentication. A signed JWT lets a service verify claims without looking up a session record on every request, but the signature does not encrypt its contents. The verifier must enforce the expected signature algorithm, issuer, audience, and expiration. A stolen JWT may remain usable until it expires unless the system has an effective way to revoke it. That makes long token lifetimes costly.
Browser tokens in localStorage are accessible to JavaScript running in that origin, including injected scripts. Putting tokens in cookies changes the exposure and brings cookie and CSRF considerations. Neither choice makes a vulnerable application safe; use the client architecture and threat model to decide.
| Approach | Typical fit | Main operational concern |
|---|---|---|
| Server-side session cookie | Conventional browser application | Protecting cookies and maintaining session storage |
| Short-lived bearer access token | API clients and distributed services | Secure storage and handling of stolen tokens |
| Signed JWT | Services that need verifiable claims | Strict validation and revocation design |
These approaches can coexist. An application might use a server-side session for its browser users while its backend calls another service with a short-lived token.
Adding a second factor
Multi-factor authentication (MFA) asks for evidence from a category other than the password. A one-time code from an authenticator app is one option; a hardware security key is another. Two passwords do not count as MFA. Email or SMS codes can help in some settings, but mailboxes and phone numbers can be compromised, and SMS is vulnerable to number-transfer attacks.
Time-based one-time passwords (TOTP) are widely supported, but a user can still type a code into an impostor site. WebAuthn security keys and passkeys use a cryptographic challenge tied to the site’s origin, making them more resistant to that kind of credential phishing. Give MFA enrollment, device replacement, and recovery codes the same attention as login. If an attacker can add a new factor through weak recovery, the original factor offers little protection.
Passkeys and federated sign-in
A passkey uses WebAuthn public-key cryptography. At registration, the authenticator creates a key pair for the site. The site stores the public key; the private key stays with the user’s authenticator, whether that is built into a device or is a separate security key. At login, the authenticator signs a fresh challenge after local user verification, such as a device PIN or biometric check. The server verifies the response with the stored public key. The site does not receive biometric data.
Passkeys can replace the password for routine sign-in and are bound to the legitimate site origin. Availability across devices depends on the authenticator and whether credentials are synchronized. Recovery still needs a deliberate design: a weak fallback password reset can undermine the stronger sign-in method.
Federated sign-in lets an application rely on an identity provider to authenticate the user. OpenID Connect (OIDC) commonly conveys identity information on top of OAuth 2.0; OAuth alone describes delegated access, not proof of identity. For OIDC sign-in, the application must validate the ID token’s issuer, audience, signature, and timing claims, and bind the response to the login attempt using the protocol’s protections. It also needs a deliberate rule for linking provider identities to local accounts. Matching an unverified email address is not enough.
Federation means your application handles fewer passwords, but it depends on the provider and does not replace local authorization. A user authenticated by the provider still needs your application’s role and resource checks.

Choosing and testing a method
For a small browser-based application, a maintained authentication library with server-side sessions is often simpler than building a token system. Consider passkeys or MFA when account compromise could expose sensitive information or permit important actions. If the product has mobile clients or independent APIs, document which client holds each credential, how long it lasts, and how it can be revoked. Prefer established protocol implementations over hand-written cryptography.
- Map the account lifecycle: include signup, login, logout, password or passkey enrollment, recovery, and account deletion.
- Define expiry: decide when sessions end and whether sensitive actions require fresh authentication.
- Protect boundaries: check authorization on every relevant server-side request, not only at login.
- Test failures: verify expired credentials, revoked sessions, repeated login attempts, and recovery links that are reused or past expiry.
- Limit disclosure: keep passwords, session IDs, access tokens, and reset tokens out of logs and analytics.
In an authorized test environment, sign in and copy the session cookie into a separate test browser profile. Then log out in the first profile and try a request from the second. If logout is meant to revoke that session immediately, the request should fail. Compare the result with the documented session policy: deleting a browser cookie alone does not revoke a server-side session.
