MeshDash Docs
R2.0
/
Home API Reference API Authentication

API Authentication

API Reference authentication jwt login token bearer cookie bcrypt session setup initial setup
How authentication works — JWT tokens, login endpoint, cookie session, and the setup flow.

MeshDash uses JWT (HS256) tokens stored in an HttpOnly cookie called access_token. All protected endpoints check this cookie. When PUBLIC_MODE=true (the default until setup is complete), authentication is bypassed entirely.

Login

Submit credentials as a standard HTML form POST. On success, the server sets the access_token cookie and redirects to /.

POST /login
Content-Type: application/x-www-form-urlencoded

username=admin%40example.com&password=yourpassword

Success: HTTP 302 → / with Set-Cookie: access_token=Bearer eyJ…

Failure: HTTP 302 → /login?error=Invalid+Credentials

Using the Token in API Calls

The cookie is sent automatically by browsers. For API clients (scripts, curl, Postman) you must send the cookie header explicitly:

# Example with curl — use the token value from the Set-Cookie response
curl -b "access_token=Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
     http://device-ip:8000/api/nodes
There is no Authorization: Bearer header support — the token must be in the access_token cookie. This is by design to prevent CSRF token leakage.

Token Lifecycle

Tokens expire after AUTH_TOKEN_EXPIRE_MINUTES (default 7 days). The /api/status endpoint silently refreshes the token when less than half its lifetime remains — so any frontend that polls status will keep sessions alive automatically.

Logout

GET /logout

Deletes the access_token cookie and redirects to /login.

Initial Setup (First Boot)

When no users exist in the database, the /setup page is served. It submits to:

POST /api/system/config/initial-setup
Content-Type: application/json

{
  "username": "[email protected]",
  "password": "securepassword",
  "AUTH_SECRET_KEY": "64hexchars...",
  "AUTH_TOKEN_EXPIRE_MINUTES": 10080,
  "MESHTASTIC_CONNECTION_TYPE": "SERIAL",
  "MESHTASTIC_SERIAL_PORT": "/dev/ttyACM0",
  "WEBSERVER_HOST": "0.0.0.0",
  "WEBSERVER_PORT": 8000,
  ...
}

What it does:

  • Creates the admin user (bcrypt-hashed password stored in SQLite)
  • Writes the full config to .mesh-dash_config
  • Deletes static/.new to permanently disable the setup endpoint
  • Returns a JWT cookie so the user is logged in immediately
  • Hot-reloads key globals without a restart
If you attempt POST /api/system/config/initial-setup when users already exist, the server returns 400 System already initialized and forcibly deletes static/.new.

C2 Internal Token

The remote C2 bridge generates short-lived (30 s) internal tokens to make proxy requests to the local API:

{"sub": "__c2_bridge__", "internal": true}

These are valid only for the duration of a single proxy cycle and are never exposed externally. The dependency get_current_active_user recognises and accepts this principal.

Password Storage

Passwords are hashed using bcrypt via passlib[bcrypt] before being stored. They are never stored or logged in plain text. The INITIAL_ADMIN_PASSWORD key is removed from .mesh-dash_config immediately after the admin account is created.

Session Renewal

The /api/status endpoint (which the frontend polls every 30 seconds) checks the token's remaining lifetime. If less than half of AUTH_TOKEN_EXPIRE_MINUTES remains, a fresh token is silently issued and set in the response cookie. The user never needs to log in again as long as they use the dashboard at least once every 3.5 days (with the default 7-day expiry).

Public Mode

When PUBLIC_MODE=true in config (the default state before initial setup is completed), all authentication is bypassed:

  • The get_current_active_user dependency returns a dummy User(username="public")
  • The logout nav item is hidden in the sidebar
  • All protected API endpoints are accessible without a token
  • Databases are ephemeral (:memory: SQLite) — data is not persisted to disk
  • The radio still connects and all real-time features work normally
Public mode is intended for demo deployments or temporary use before setup. Never run PUBLIC_MODE=true on an internet-exposed instance — there is no authentication whatsoever.

Login Redirect

Protected page routes (/, /map, /settings, etc.) check the access_token cookie server-side. If the token is missing, expired, or invalid, the server returns HTTP 302 → /login. The browser follows the redirect and shows the login page. There is no AJAX-based auth check on these page routes — the redirect is handled entirely server-side.

Multi-User Support

The users table supports multiple accounts. All users have equal access — there are no role distinctions beyond disabled flag. To create additional users after initial setup, use the sqlite3 CLI to insert a bcrypt-hashed password directly, or add a user creation API endpoint via a plugin.

# Generate a bcrypt hash for a new user (Python)
python3 -c "from passlib.context import CryptContext; print(CryptContext(['bcrypt']).hash('newpassword'))"

# Insert into DB
sqlite3 meshtastic_data.db "INSERT INTO users (username, hashed_password) VALUES ('[email protected]', '\$2b\$12\$...');"

Token Security Notes

  • Tokens are HS256 JWT signed with AUTH_SECRET_KEY
  • The cookie is httponly=True — JavaScript cannot read it, preventing XSS token theft
  • The cookie is samesite="lax" — protects against CSRF for state-changing requests
  • Changing AUTH_SECRET_KEY instantly invalidates all existing sessions (all users are logged out)
  • The C2 bridge uses a separate short-lived (30 s) internal token that is never sent to the browser