Why the browser never talks to our API
In the applications we build, the browser holds no readable token and never calls the API directly. Here is how that works and what it costs.
When you sign in to the client portal on this site, your browser never receives an access token it can read, and it never calls our API directly. Every request goes through the web server first. This is how we build most client portals and dashboards, and here is why.
The usual setup and its problem
A common way to build a web app is a single-page application that calls a JSON API from the browser. The app has to keep a token somewhere it can reach, which means any script running on the page can reach it too. One vulnerable dependency or one injected script is enough to walk away with the session.
What we do instead
The website has a server side, and that server is the only client of the API. The pattern is often called a backend for frontend.
- The session lives in two cookies marked
HttpOnly, so JavaScript on the page cannot read them. In production they are alsoSecure. - The access token lasts 15 minutes. A second, longer-lived token renews it, and it is replaced every time it is used.
- If an old renewal token ever shows up again, we treat it as stolen and close every session that descends from it.
- Pages are rendered on the server with the data already in them. Forms post to the server, which validates the input and then calls the API.
The API itself still checks every request: who is calling, what role they have, and how often they are calling.
What it costs
There is one more hop between the browser and the data, and the web server has to be running for anything to work. Rate limits need care too: the API sees the web server, not the visitor, so the web server passes the visitor's address along and the API limits by that.
For an application that handles client data, that is a price we are happy to pay. The browser holds nothing worth stealing, the API has no cross-origin surface to defend, and content pages still work with JavaScript turned off.