Login¶
The login screen is the entry point to the simulator UI when authentication is enabled. With BYPASS_AUTH=true (the default in the example docker-compose.yml), you never see it - the URL bar takes you straight to the Dashboard Overview.
This page documents the login screen and the auth model behind it. If you're configuring a deployment, the security trade-offs live on the Security reference page.
The screen¶
| Element | Default | What it is |
|---|---|---|
| Logo + title | "GPS Emulator" with the LOFT logo | Branding. Replaced by editing frontend/public/LOFT_logo_130x100.png and rebuilding the frontend. |
| Username field | empty (placeholder admin) |
Auto-completed by the browser if you've logged in before. |
| Password field | empty | Standard type="password" field. |
| Sign in button | enabled when both fields are non-empty | Shows a spinner while the POST to /api/auth/login is in flight. |
| Error banner | hidden | Red banner with an alert icon, surfaces "Invalid credentials" or other login failures. |
| Footer | version + author | Static, comes from the React build. |
The background is a world-map GIF (map_background.gif) that follows the theme - a dark overlay layers over it in dark mode. The theme is read from localStorage on mount, so if you set it in a previous session it carries forward to the login screen.
How authentication works¶
| Layer | Where | Detail |
|---|---|---|
| Credentials | USERNAME / PASSWORD env vars |
Read by backend/config.py at container start. Defaults: admin / changeme. |
| Verification | POST /api/auth/login |
Compares submitted creds to the env-var values. Constant-time comparison via secrets.compare_digest. |
| Session storage | In-memory dict in backend/auth.py |
Token is a UUID4. Sessions evaporate on container restart. |
| Session transport | Cookie gps_session |
HttpOnly, SameSite=Lax. Not Secure (the container speaks plain HTTP; put a TLS reverse proxy in front of it for production). |
| Session check | GET /api/auth/check |
Front-end calls this on mount via useAuth.jsx to decide whether to render Login or Dashboard. |
| Logout | POST /api/auth/logout |
Deletes the server-side session record and clears the cookie. Triggered by the logout icon in the header on the Dashboard. |
BYPASS_AUTH semantics¶
BYPASS_AUTH |
What happens |
|---|---|
true (default in the example compose file) |
Every /api/auth/check call returns {authenticated: true}. The Login route effectively never gets rendered - the React app skips straight to the Dashboard. /api/* endpoints accept every request. |
false |
Standard cookie-session flow as above. Unauthenticated /api/* calls return 401. |
| Any other value | Treated as falsy. The simulator does not parse "yes" / "no" or any other variant - only the exact string "true" flips the bypass on. |
Do not run BYPASS_AUTH=true on a network you don't control
The /api/control endpoint can start NMEA emission to connected hardware. The /api/position endpoint can move the position arbitrarily. Anyone who can reach the UI with bypass on can do both. Treat the bypass strictly as a closed-lab convenience and never as a production setting.
What happens on a successful login¶
- POST to
/api/auth/loginwith the form values. - Server validates, creates a session, sets the cookie, returns
{success: true, message: "Login successful"}. - React calls
navigate('/')and the Dashboard renders.
What happens on a failed login¶
| Cause | What you see |
|---|---|
| Wrong username or password | Red banner: "Invalid credentials". Form remains; both fields preserved. |
USERNAME or PASSWORD env var was empty at container start |
Effectively every login fails with "Invalid credentials" because the constant-time comparison can never match an empty string. Set both env vars before exposing the UI. |
| Server error (database, network) | Banner shows whatever message the server returns. The form remains. |
The login form does not rate-limit on the front end. The container also does not impose its own rate limit. Run a reverse proxy (nginx, Caddy, Cloudflare) with rate limiting in front of the UI for production.
Session lifetime¶
| Trigger | What happens to the session |
|---|---|
| Operator clicks logout in the Dashboard header | Server-side record is deleted, cookie is cleared. Next API call returns 401, the React app falls back to Login. |
| Container restarts | The in-memory session dictionary is wiped. Every existing cookie becomes orphaned. Next API call returns 401, every connected operator is bumped to Login. |
| The cookie expires | The cookie has no explicit Max-Age / Expires, so it lives until the browser session ends. Once the browser is closed, the cookie is gone. |
| The cookie is forged or tampered with | The session token won't match any record on the server. Treated as 401. |
There is no concept of "session refresh" or "remember me" - by design the simulator is meant to be operated by someone actively at the console.
Persistent state¶
| Setting | Env var | Effect |
|---|---|---|
| Username | USERNAME |
Login credential. Required for non-bypass auth. |
| Password | PASSWORD |
Login credential. Required for non-bypass auth. |
| Bypass auth | BYPASS_AUTH |
true skips the Login screen entirely. |
The login screen itself has no persistent state - what you type is in memory until you submit.
What's next¶
- Dashboard Overview - the screen you land on after a successful login.
- Common UI Elements - the header, theme toggle, logout button (all on the Dashboard side).
- Security - the full hardening checklist including TLS termination, rate limiting, and the bypass-auth trade-off.