Yes. A single-page application can use OAuth 2.0 with ordinary browser JavaScript and a static web host. Use Authorization Code with PKCE, treat the browser as a public client with no secret, register an exact redirect URI, and choose deliberately between browser-only token handling and a backend-for-frontend (BFF). OAuth obtains tokens; your API must still decide what each user is allowed to do.
First separate OAuth from application authorization
OAuth gives a browser application an access token that it can present to a resource server. It does not automatically grant permission for every operation. Your API must validate the token and apply its own policy for the user, tenant, resource, action, and any relevant scopes or claims. This distinction is consistent with the OWASP Authorization Cheat Sheet and with the IETF draft OAuth 2.0 for Browser-Based Applications.
The IETF document used here is draft 27, dated July 2026, and expires on 7 January 2027. It is current draft guidance rather than a final RFC, so check the latest version when you implement or review the design.
Choose where OAuth responsibilities live
The absence of Node.js or a JavaScript framework does not determine the architecture. A BFF is a server role, not a Node.js product; it can be implemented with any suitable server technology. Conversely, a static site can perform OAuth entirely in the browser.
#1 Best Overall
| Architecture | Where tokens are handled | How API calls travel | Main advantage | Main cost or exposure |
|---|---|---|---|---|
| Browser-only public client | The browser exchanges the authorization code and handles access tokens; refresh tokens, if issued, are also exposed to browser-side handling. | The browser calls the resource server directly with an access token. | No application backend is required; a static host is sufficient. | Token handling occurs in the browser, where malicious JavaScript can attack the application context. |
| Token-mediating backend | An intermediate backend participates in token handling between the browser and authorization server. | Some requests pass through the backend; exact routing and storage depend on the design. | Provides an intermediate compromise between direct browser handling and a full BFF. | It has different request-routing and token-exposure trade-offs and must not be treated as identical to a BFF. |
| Backend for Frontend (BFF) | The BFF exchanges the code, associates tokens with a server-side session, and keeps OAuth tokens out of browser code. | The browser calls the BFF; the BFF adds the access token when forwarding to the resource server. | Browser JavaScript cannot directly read the BFF-managed OAuth tokens. | Extra deployment, scaling, maintenance, and security responsibility; resource requests are routed through the BFF. |
A BFF reduces direct token extraction from browser code, but it does not make a compromised browser harmless. Malicious code can still use the user’s live BFF session to send authenticated requests. The draft also warns that a BFF vulnerability can have significant impact because it concentrates security responsibility.
Implement a browser-only SPA on a static host
1. Register a public client
Register the application as a public browser client. Do not put a client secret in HTML, JavaScript, a source map, or any value delivered to users; everyone who receives the application can inspect it. Register the exact callback URI you will use, including scheme, host, path, and any required port. Do not rely on wildcard or loosely matched redirect registrations.
2. Generate a PKCE verifier and challenge
For every authorization attempt, create a high-entropy code_verifier and derive an S256 code_challenge. Authorization Code with PKCE is the recommended flow for browser applications. The draft says public browser clients using this flow must implement PKCE and authorization servers must support and enforce it.
const randomBytes = (length) => crypto.getRandomValues(new Uint8Array(length));
const base64url = (bytes) => btoa(String.fromCharCode(...bytes))
.replace(/+/g, '-').replace(///g, '_').replace(/=+$/, '');
const verifier = base64url(randomBytes(32));
const digest = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(verifier)
);
const challenge = base64url(new Uint8Array(digest));
Keep the verifier associated with this browser tab until the callback arrives. Use an appropriate browser mechanism for that short-lived state and remove it after the exchange; do not treat any storage mechanism as immune to malicious code running in the application context.
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #2
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
3. Bind and protect the redirect request
Create a unique, unguessable state value, retain it for the callback, and reject a response whose state does not match. The draft identifies enforced PKCE, a unique verified OAuth state, or, for OpenID Connect, a verified nonce as protections against redirect-flow CSRF. Use the mechanisms required by your protocol and verify them before processing the authorization code.
4. Redirect to the authorization endpoint
Send the user to the authorization server with parameters equivalent to:
response_type=code- the registered
client_id - the exact
redirect_uri - the requested scope
- the generated
state code_challengeandcode_challenge_method=S256
The authorization server authenticates and obtains the user’s consent according to its policy, then redirects back with a short-lived authorization code.
5. Verify the callback and exchange the code
On the callback route, first verify the returned state (and nonce when using OpenID Connect). Reject errors, unexpected issuers, and an unrecognized redirect context. Exchange the one-time code at the token endpoint, sending the original code_verifier, client ID, and exactly the same redirect URI. Never send a client secret because this is a public client.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Rank #3
6. Call the resource server
Keep the access token only as long as your threat model permits and send it over HTTPS in the resource server’s expected authorization header:
const response = await fetch('https://api.example.test/profile', {
headers: { Authorization: `Bearer ${accessToken}` }
});
The resource server must validate the token and then enforce whether this user may perform the requested operation. A successful token exchange or login is not an authorization decision for every endpoint.
Make browser token storage a threat-model decision
Storage choices change exposure; none makes malicious code in the application context safe. Widely accessible storage such as Local Storage is easier for malicious JavaScript to reach than more isolated arrangements such as a Web Worker. That is a relative isolation difference, not a guarantee that a Web Worker defeats an attacker who can execute code in the application context.
- Short-lived in-memory handling: limits persistence but requires a new flow after a reload or tab loss.
- More isolated browser arrangements: can reduce direct access from ordinary page scripts, but still require careful message handling and do not neutralize XSS or compromised remote code.
- Local Storage: convenient and persistent, but readily accessible to JavaScript running in the page.
Choose based on the damage an attacker could cause, how often users must reauthenticate, and whether your application can tolerate losing tokens on reload. Do not claim that any browser storage option eliminates token theft.
Rank #4
- Brand: Wiley
- Set of 2 Volumes
- A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
Use a BFF when you want server-managed tokens
With a BFF, the browser begins authorization through the BFF rather than exchanging the code itself. The BFF performs the code exchange, associates the resulting tokens with the user’s server-side session, and sets a session cookie. Later browser requests go to the BFF; it adds the access token when forwarding to the resource server. The browser never receives the OAuth access or refresh token directly.
- The browser navigates to a BFF authorization route.
- The BFF creates and records the authorization transaction, then redirects to the authorization server with PKCE and the redirect URI.
- The authorization server returns the code to the BFF callback.
- The BFF verifies the transaction and exchanges the code.
- The BFF stores or otherwise protects the token association on the server and sets a cookie with both
SecureandHttpOnly. - The browser calls BFF endpoints; the BFF forwards permitted requests with the access token and returns the resource response.
This design adds a backend that must be deployed, scaled, monitored, patched, and protected. It also means resource requests pass through that backend. A BFF limits direct token reading by browser code, but XSS can still issue requests through the authenticated session, so API authorization and session controls remain essential.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Handle refresh, logout, and expiry explicitly
Refresh tokens
If a browser client receives refresh tokens, treat them as more persistent credentials than access tokens. The IETF draft calls for rotation on every use or sender-constrained refresh tokens, plus a maximum lifetime or expiration after inactivity. A rotated refresh token must not extend beyond the established initial lifetime. Enforce the authorization server’s reuse and revocation behavior, and discard client-side copies when the session ends.
Logout and session expiry
Logout should clear the application’s local session state. In a BFF, invalidate the server-side session and its token association, then expire the cookie. Access-token expiry and refresh failure must return the application to a signed-out state rather than leaving a loop of failed API calls. Provider-side logout behavior is deployment-specific; do not assume that clearing a browser variable revokes a token at the authorization server.
Best Value
Security checklist for a framework-free implementation
- Use Authorization Code with PKCE and S256 for the public browser client.
- Never ship a client secret in browser-delivered code.
- Register and use an exact redirect URI; reject unexpected callback destinations.
- Generate and verify a unique
statevalue for each flow, or verify an OpenID Connectnoncewhere applicable. - Use HTTPS for the application, callback, token exchange, and API requests.
- Document where access and refresh tokens live and which scripts can reach them.
- If refresh tokens reach the browser, require rotation or sender constraint and a bounded lifetime or inactivity expiry.
- Assume XSS or compromised remote JavaScript can act as the user, even when a BFF hides token values.
- Make the resource server enforce user- and resource-level permissions instead of trusting login success.
- Review the latest IETF draft before release because draft requirements can change.
Which design should you choose?
Choose browser-only when
A static deployment is important, your team accepts browser token exposure, and the resource server can safely accept calls from the browser. Keep access tokens short-lived where practical and make the storage and refresh trade-off explicit.
Choose a BFF when
You want OAuth tokens kept out of browser-readable state, can operate a backend, and accept routing API traffic through it. Treat the BFF as a high-value security boundary rather than as a token-hiding shortcut.
Evaluate token mediation separately
A token-mediating backend can fit systems that need an intermediate arrangement, but its exact security properties depend on which requests it handles and where tokens reside. Document those details instead of labeling it a full BFF.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




