Device tokens (Tier 1)
HS256 JWTs with per-token revocation. The starting point for any fleet.
Device tokens are the recommended auth for any new robot integration. They cost nothing to provision, they fit in a few kilobytes of robot config, and any compromised token can be revoked instantly without rotating the platform secret.
Token shape
A standard JWT signed with HS256:
{
"alg": "HS256",
"typ": "JWT"
}
.
{
"iss": "waveform-nav-pack",
"sub": "robot:<uuid>",
"customer_id": "<uuid>",
"tier": "robot:tier1",
"fleet_id": "depot-north",
"scopes": ["nav_pack:read"],
"iat": 1700000000,
"nbf": 1700000000,
"exp": 1731536000,
"jti": "<uuid>"
}Key fields:
subβ namespaced asrobot:<uuid>so it canβt collide with human user ids.customer_idβ gets compared torobot_devices.owner_customer_idon every verify. Mismatch = 403.scopesβ closed enum:nav_pack:readandnav_pack:audit:readtoday.jtiβ the revocation identifier. Lets us kill one token without rotating the HMAC key.expβ default TTL is 30 days; min 1 minute, max 180 days.
Issuing a token
Admin-only endpoint. Returns the JWT exactly once.
POST /v1/robots/<robot_id>/tokens
Authorization: Bearer <admin-cognito-jwt>
Content-Type: application/json
{
"scopes": ["nav_pack:read"],
"ttl_seconds": 2592000
}Response:
{
"status": "ok",
"data": {
"jti": "...",
"token": "eyJhbGciOiJIUzI1...",
"issued_at": "2026-05-28T00:00:00Z",
"expires_at": "2026-06-27T00:00:00Z",
"scopes": ["nav_pack:read"]
}
}Cache the token on the robot at this moment β the server never returns the raw JWT again.
Using a token
Send it as a bearer token on any nav-pack read endpoint:
GET /v1/areas/<area_id>/nav-pack
Authorization: Bearer eyJhbGciOiJIUzI1...The server verifies the signature, checks exp + nbf with 30 s leeway, confirms jti isnβt in the revocation set, and scopes the request to the tokenβs customer_id. ACL on every downstream query gets it for free.
Revoking a token
If a robot is lost / stolen, revoke its tokens individually:
POST /v1/robots/tokens/<jti>/revoke
Authorization: Bearer <admin-cognito-jwt>
Content-Type: application/json
{ "reason": "device reported stolen" }The endpoint updates robot_device_tokens.revoked_at and pushes the jti into the in-process revocation deny-list for the API instance handling the request. Sibling instances pick up the revocation via a periodic refresh from the DB (default cadence: 5 min).
DELETE /v1/robots/<id> automatically revokes every active token issued to that robot, in one atomic transaction. You donβt need to revoke each jti separately.Rotating the HMAC secret
The HMAC secret lives in AWS Secrets Manager at waveform/<env>/robot-token-hmac-key. To rotate:
- Stage the new secret as a separate version of the same secret. Production verifiers accept both during a TTL window.
- Re-issue tokens for every active robot, signed by the new version.
- After the issued-with-old-key tokens have all expired (max 30 days at default TTL), retire the old version.
Rotation does not require downtime; the verifier handles multi-version checking.
Threat model
Read the design doc for the full table; the short version:
- Token theft from one robot β per-token
jtirevocation handles it. - Token replayed across customers β the server checks
customer_idmatches the robotβs row. - HMAC secret leaked β rotate via the dual- validate window above.
- Robot impersonates another β
jti+subare unique; impersonation requires forging the HMAC.