Skip to content

Authentication

client ──TLS handshake──▶ proxy layer 1: client certificate
│ signed by your private CA
▼
api layer 2: Authorization: Bearer <token>
Layer Where Failure looks like
Client certificate Reverse proxy, during the TLS handshake The connection does not complete. No HTTP status, because there is no HTTP.
Device token The API 401 with a problem document.

Both are required on the public interface. The browser dashboard, reachable only over LAN or VPN, presents the token alone — there the network is the first layer.

A JWT signed with HELSA_JWT_SECRET, issued on the server by an operator command:

Terminal window
make prod-token SUBJECT=iphone

There is deliberately no HTTP endpoint that issues tokens. An endpoint that mints credentials is an endpoint an attacker can call; a command on the host is not.

Send it on every request:

GET /v1/summary?range=day&metrics=stepCount HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Lifetime is long by design. The reasoning — and the revocation options — are on the Device token page.

These exist for a session that rotates. ⚠️ The dashboard is not one of them: it keeps the device token you paste into it in localStorage, like any other device. There is no cookie and no browser-specific session — one more reason 8443 stays off the internet.

  • POST /v1/auth/refresh — exchange a refresh token for a new session. No bearer token needed.
  • POST /v1/auth/logout — ends the calling device’s session: drops the refresh token and revokes the access token presented on the request. 204, or 503 if the deny-list could not be written — because answering success when the session is still alive would be the worst possible lie here.

Native clients normally use their long-lived device token and never touch either. To revoke a token from a device you no longer have, use the CLI — see revoking.

Client certificate details reaching the application

Section titled “Client certificate details reaching the application”

The proxy forwards the certificate subject and serial as headers so the API can record which device is talking:

X-Client-Cert-Subject: CN=iphone,O=Helsa
X-Client-Cert-Serial: ...
Terminal window
curl --cacert ca.crt \
--cert client.crt --key client.key \
-H "Authorization: Bearer $HELSA_TOKEN" \
'https://helsa.example.net/v1/summary?range=week&metrics=stepCount&tz=Europe/Budapest'

In code:

  1. Load the client identity — a PKCS#12 bundle or a certificate/key pair — into your TLS configuration.
  2. Pin your CA rather than using the system trust store. The server certificate is not signed by a public authority, and pinning also rules out a rogue public CA.
  3. Send the bearer token on every request.
  4. Treat 401 as “get a new token” and a TLS failure as “fix the certificate”. They are different problems with different fixes.

RFC 9457 problem documents:

{
"type": "about:blank",
"title": "unauthorized",
"status": 401,
"detail": "missing or invalid bearer token"
}
Status Meaning
401 No token, malformed token, wrong signing secret, or revoked. A revoked token says so in the body, rather than “invalid token” — somebody whose phone was stolen should not be sent to check their typing.
404 Unknown route, or a resource that does not exist.
413 Ingest chunk too large. Split it and retry.
501 In the contract but not implemented — currently only PDF export.
503 /readyz when a dependency is unreachable — and any /v1 request when the revocation deny-list cannot be read. Nothing is wrong with the token; retry.