Skip to content

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 id
  • localStorage["user_email"] — current user email

These are convenience caches; the source of truth is the User returned by /me.

Login flow

  1. User submits LoginFormuseAuth().login(email, password)
  2. AuthContext.login() POSTs { email, password } to ${ENV.API_URL}/webauthenticate with Apptype: <base64> header
  3. On 2xx: store auth_token, useid, user_email; set user state; navigate("/accounts")
  4. On error: re-throw; LoginForm shows 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() in api.ts throws + 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

  1. Reconcile response field names with the BE; pick one shape.
  2. Add a 401 → logout handler in api.ts.
  3. Add a storage-event listener for multi-tab sync.
  4. Migrate to httpOnly cookie for the token (BE + FE change).
  5. Encode token expiry server-side; FE refresh / re-login on expiry.
  6. Add a server-side /logout call that adds the token to a DB-backed revocation table.