How the panel talks to the backend
The management panel is a separate application. It does not share a process with the backend, does not share a database and does not import its code: it speaks to it over HTTP, like any other program would.
This page explains why it is built this way, which is the part you cannot work out from reading the code.
The token is not in the browser
Section titled “The token is not in the browser”This is the decision everything else hangs from.
The usual thing in an admin panel is to keep the session token in
localStorage and send it as Authorization: Bearer …. It works, it is
convenient, and it has a problem you do not see until it is late: any
execution of somebody else’s JavaScript in the panel walks off with it. A
compromised dependency, a text field rendered without escaping, a browser
extension. And with that token you get in from anywhere, with no password and
without leaving a single failed attempt in a log anyone could look at.
Here the token lives on the panel’s own server. The browser only holds a cookie:
HttpOnly · Secure · SameSite=Strict · __Host- prefixHttpOnly means the page’s JavaScript cannot read it. Stealing the token stops
being possible because there is nothing in the browser to steal.
What each part of that cookie does
Section titled “What each part of that cookie does”HttpOnly |
the page’s script cannot see it |
Secure |
it never travels outside HTTPS |
SameSite=Strict |
it is not sent on requests coming from another site, which is what stops CSRF |
__Host- |
the browser ties it to this exact domain and to /; a subdomain cannot overwrite it |
⚠️
__Host-requiresSecure, and the browser silently rejects a__Host-cookie without it. No error, no warning: signing in looks fine and the session is never stored — you log in, and you are back at the login screen. That is why the prefix is only applied when the connection allows it (localhost, or real HTTPS as seen throughX-Forwarded-Proto). If your proxy does not send that header, there will be no prefix, and it is worth knowing before you lose an afternoon.
Calls go through the panel itself
Section titled “Calls go through the panel itself”Screens do not call the backend: they call /admin/api/backend/*, which is a
route of the panel. There, on the server, the cookie is read, the token is taken
out and the request is forwarded.
That has three consequences worth more than the convenience it costs:
- CORS stops being necessary. The browser talks to its own origin. There is
no
ADMIN_CORSto tune and no origins to authorise. - The backend is not exposed to the browser. Only the panel speaks to it.
- Routes are on a server-side allowlist. The proxy does not forward wherever it is told: it forwards what it is allowed to. A tampered parameter does not turn the panel into a bridge to another machine.
The client is ours, and why
Section titled “The client is ours, and why”@pcreative/admin-client. No dependencies.
There used to be a third-party SDK, and it was replaced after looking at actual usage: of the panel’s calls, well over half were a plain HTTP request and the rest spread across some thirty methods. In exchange for that thin wrapper, dozens of the panel’s files were tied to an external dependency and to its way of naming things.
What did have to be replicated exactly is the shape of the request, because that is not a design decision: it is what the backend expects on the other side.
- The query string is serialised with brackets:
filter[field]=value, and lists are indexed,ids[0]=a&ids[1]=b. - Nulls are omitted.
- Cookies travel.
- The error carries the backend’s message, not the generic one for the status code.
Getting any of those four wrong produces no error at all: a filter that matches nothing is sent, the backend answers 200 with an empty list, and the screen says “no products” over a full catalogue. That is why the client has tests about the format and not about whether it “works”.
One panel, several shops
Section titled “One panel, several shops”The backend being spoken to is resolved from the active theme: activate a theme, reload, and the panel starts talking to that theme’s backend.
The session belongs to each backend, so changing shop means signing in again. That is not an oversight: a session that worked across several shops would turn the panel into a master key.
To deploy it
Section titled “To deploy it”The Apache configuration, with and without subdomains, is in the internal notes
on panels and domains. The one thing you cannot forget is X-Forwarded-Proto,
for the reason above.