Sessions and the website hand-off
Sign-in paths
| Path | How |
|---|---|
Email + password on /login | POST /rest/api/token/ |
Google on /login | GoogleButton posts the ID token to POST /rest/auth/google/ |
| From the website | /auth/handoff exchanges a one-time code (below) |
| Sign-up | Redirects to the website's /account/sign-up?next=app when VITE_WEBSITE_URL is set, so the website owns account creation and hands the new account straight back |
The hand-off
src/pages/HandoffPage.jsx renders before the auth gate in src/App.jsx, and bootstrapAsync skips its usual refresh on that path. Expired or replayed links show a toast and land on /login. The code travels in the URL fragment, never a query string, so it never reaches a server log.
The backend only honours exchanges from origins in APP_ALLOWED_ORIGINS (+ APP_EXTRA_ORIGINS). Contract: docs/WEBSITE_ACCOUNTS.md.
Refresh and logout
src/store/AxiosInstance.jsx (withCredentials: true):
- On 401: refresh with the body token (
POST /rest/api/token/refresh/) and retry once; if that fails, log out. - On 429: show a toast.
- Sign-in requests pass
{skipAuthRefresh: true}.
Logout calls POST /rest/api/token/logout/ so the refresh token is blacklisted server-side.
Deploy order
When the hand-off contract changes: backend, then this app, then the website.