Authentication
Authentication in Piral comes down to one question: how does a request prove who the user is? There are two broad answers, and the choice shapes how much the app shell and pilets have to do.
- Cookie-based (via a BFF) — a backend holds the session and the browser carries an HttpOnly cookie automatically. Recommended for most apps.
- Token-based — the app shell obtains an access token and attaches it to each request.
A useful rule of thumb: if you control the backend the frontend talks to, prefer the cookie/BFF approach. Reach for tokens when you must call APIs the BFF can't sit in front of.
Cookie-based authentication with a BFF (recommended)
A Backend-For-Frontend (BFF) is a small server that sits between the browser and your backend services. The user authenticates against the BFF, which keeps the session server-side and sets a secure, HttpOnly cookie. From then on the browser attaches that cookie to every request automatically — so neither the app shell nor any pilet has to manage tokens at all.
Inside a pilet, calls to your backend simply work — the cookie rides along:
Why this is the default recommendation:
- More secure. The token never lives in JavaScript or storage, so it can't be stolen via XSS. HttpOnly + Secure + SameSite cookies are handled by the browser.
- More reliable. No token refresh logic in the frontend, no expiry races; the BFF manages the session lifecycle.
- Easier. The app shell does essentially no auth work, and every pilet's
fetchjust works without a plugin or shared token.
The trade-off is flexibility: every backend call has to be reachable through the BFF (directly or proxied). For the large majority of line-of-business portals that's perfectly fine — and well worth the security and simplicity it buys.
With a BFF you usually don't need an auth plugin at all. Plugins like piral-oidc exist to bring tokens to the Pilet API — if your requests are authenticated by a cookie, there's no token for pilets to consume.
Token-based authentication
When a BFF isn't an option — for example pilets must call third-party or cross-origin APIs directly — the app shell obtains an access token (often via OpenID Connect) and attaches it to requests as an Authorization header.
Letting pilets use the token
Pilets shouldn't each re-implement auth. Expose the token (and an authenticated fetch) through a small plugin in the app shell — this is exactly what an auth plugin is for:
Using an official plugin: piral-oidc
Rather than wiring OIDC by hand, the piral-oidc plugin surfaces tokens and sign-in/out to pilets through the API:
Be aware of the costs token-based auth brings: the token is reachable from JavaScript (an XSS risk), you must handle refresh and expiry, and every pilet that calls a protected API needs the token. These are the very problems the cookie/BFF approach avoids.
Feed-level access control
Independent of how requests are authenticated, the feed service can return different pilets for different users — the strongest form of access control, because users never even download code they aren't allowed to run:
See Feed service → per-user targeting.
Pilet route guards
For in-app gating, a pilet can guard its own routes — useful for hiding UI, though it is not a substitute for server-side checks: