Authentication (Client-side)¶
Token storage¶
localStorage["auth_token"] — the encrypted token returned by Someli-admin-api's /webauthenticate.
const getAuthToken = (): string | null => localStorage.getItem("auth_token");
const setAuthToken = (token: string): void => localStorage.setItem("auth_token", token);
const removeAuthToken = (): void => localStorage.removeItem("auth_token");
This is XSS-readable. Any successful XSS on admin.someli.ai can exfiltrate the token. For an internal tool this is moderate-impact (only staff get XSS'd), but the token has no expiry server-side so the impact is long-lived. See security.md.
httpOnly cookies would be the better store. Switching would require a coordinated BE change (set the cookie on /webauthenticate; read it in middlewares/auth.js).
Additional localStorage keys¶
Set by AuthContext.tsx login():
localStorage["useid"]— current user idlocalStorage["user_email"]— current user email
These are convenience caches; the source of truth is the User returned by /me.
Login flow¶
- User submits
LoginForm→useAuth().login(email, password) AuthContext.login()POSTs{ email, password }to${ENV.API_URL}/webauthenticatewithApptype: <base64>header- On 2xx: store
auth_token,useid,user_email; setuserstate;navigate("/accounts") - On error: re-throw;
LoginFormshows an inline error
The response shape is defensive about field names:
const userId = user.id || user.user_id || user.memberId || user.userId;
const userEmail = user.email || user.username || user.email_address;
Five potential field names for "userId" and three for "email" — a code smell that the BE response shape isn't stable / agreed. Reconcile with Someli-admin-api's /webauthenticate handler.
OAuth¶
None. There is no SSO, no Google/GitHub/Microsoft login. Only email + password.
Logout¶
const logout = (): void => {
removeAuthToken();
setUser(null);
setToken(null);
navigate("/login", { replace: true });
toast({ title: "Logged Out", ... });
};
Client-side only. No server-side revocation call. Combined with Someli-admin-api's in-memory revocation (which clears on process restart), logout is effectively just removing the local copy of the token.
If a staff member is suspended, an attacker holding their token can still use the API until either (a) the BE is restarted (in-memory revocation cleared, but token is also no longer in any set, so it's still valid), or (b) the AES key in tokenGenerator.js is rotated (which invalidates every token, including legitimate users').
This is a significant gap. See Someli-admin-api/security.md F-3 and F-4.
Token expiry handling¶
The token has no expiry. The FE never proactively checks expiry. On any 401 from the API:
request()inapi.tsthrows + toasts an error- No global handler forces logout
- The user has to manually log out + log in
Recommendation: add a 401 → logout() handler in api.ts's request().
MFA / 2FA¶
Not implemented.
Session persistence¶
The token persists across browser restarts (localStorage). There is no "remember me" toggle — login is always remembered.
Multi-tab synchronisation¶
Not implemented. Logout in one tab does not affect other tabs until they re-render and checkAuth() runs. A window.addEventListener('storage', ...) handler in AuthProvider (~5 lines) would synchronise.
CSRF¶
The FE uses localStorage + custom headers (Token, Apptype). It does not rely on cookies for auth. CSRF is therefore not exploitable against authenticated endpoints — an attacker site cannot make the browser attach the Token header (browsers don't send custom headers cross-origin).
Caveat: the BE's express-session middleware does set a cookie. If any handler ever relies on req.session, CSRF becomes possible — but currently no handler does (per Someli-admin-api/security.md).
Recommendations¶
- Reconcile response field names with the BE; pick one shape.
- Add a 401 → logout handler in
api.ts. - Add a storage-event listener for multi-tab sync.
- Migrate to httpOnly cookie for the token (BE + FE change).
- Encode token expiry server-side; FE refresh / re-login on expiry.
- Add a server-side
/logoutcall that adds the token to a DB-backed revocation table.