# Mission-Token Issuance for Disconnected UAV Operations **Task**: AZ-533_mission_token_uav **Name**: Mission-token issuance for disconnected UAV operations **Description**: New `POST /sessions/mission` endpoint that issues a single long-lived (≤11 h) access token for one specific flight. Narrowly scoped (`mission_id`, `aircraft_id`, `aud`), one-shot, auto-revoked on aircraft reconnect. Solves the "10 h offline UAV vs 15 min ground access token" tension without weakening interactive-session security. **Complexity**: 5 points **Dependencies**: AZ-531 (needs `sessions` table for revocation tracking). Can implement in parallel; final wiring depends on AZ-531. **Component**: Admin API + Services + DataAccess **Tracker**: AZ-533 **Epic**: AZ-529 ## Problem UAV missions can fly up to 10 h fully offline (no Starlink, no admin reachability). Standard short-lived access tokens (15 min) plus refresh-on-network are physically impossible during flight. Today's solution would be "set JWT lifetime to 4 h and pray", which is both too short for full missions and too long for ground operations — a single lifetime can't satisfy both. ## Outcome - New endpoint `POST /sessions/mission` (auth: existing interactive access token, MFA proven within last 15 min by virtue of refresh chain). - Body: `{ mission_id, aircraft_id, planned_duration_h, requested_scope }`. - Returns: a single long-lived access token (no refresh) with custom claims: ```json { "sub": "", "iss": "AzaionApi", "aud": "satellite-provider", "exp": "now + planned_duration_h + 1h", "mission_id": "M-2026-05-14-042", "aircraft_id": "UAV-117", "valid_region": { "...bbox..." : "..." }, "permissions": ["GPS"], "sid": "", "jti": "", "token_class": "mission" } ``` - Mission tokens are recorded in `sessions` table with `class='mission'` so logout/revocation works. - On post-flight reconnect (any successful auth call from the same `aircraft_id`), all open mission sessions for that aircraft are auto-revoked. ## Scope ### Included - `MissionSessionRequest` / `MissionSessionResponse` DTOs in `Azaion.Common/Requests/`. - Validation: `planned_duration_h` ∈ [0.1, 12]; `mission_id` matches `M-YYYY-MM-DD-NNN`; `aircraft_id` exists in users table with `Role=CompanionPC`. - Auto-revoke-on-reconnect logic in middleware (cheap: index on `sessions(aircraft_id, class, revoked_at)`). - Tests: happy path, scope-narrowing, max-duration cap, auto-revoke on next call. ### Excluded - Hardware binding (mTLS / DPoP / `cnf` claim) — separate future ticket. This ticket gets the lifetime + scope right; hardware binding is a hardening pass. - Verifier-side enforcement of `mission_id`/`valid_region`/`aircraft_id` claims — filed under satellite-provider once admin ships. - Pre-flight ground station UX (file/load mission token onto UAV) — client/UI concern. ## Acceptance Criteria **AC-1: Mission token issued with correct lifetime** Given an authenticated pilot session and `planned_duration_h=9` When `POST /sessions/mission` is called Then response includes a single access token with `exp ≈ now + 10h` (±60s), no refresh token, `token_class="mission"`. **AC-2: Hard cap enforced** Given `planned_duration_h=15` When called Then 400 with detail `"planned_duration_h must be ≤ 12"`. **AC-3: Scope claims present** Given a request with `mission_id` and `aircraft_id` When the returned token is decoded Then `mission_id`, `aircraft_id`, `aud="satellite-provider"`, `permissions`, `sid`, `jti` all present. **AC-4: Auto-revoke on reconnect** Given aircraft UAV-117 has an open mission session M-001 When UAV-117 calls any `/token/refresh` or `/login` endpoint successfully Then the M-001 mission session is marked `revoked_reason='post_flight_reconnect'` and that token stops working. **AC-5: Issued only against an authenticated session** Given no auth header When `POST /sessions/mission` is called Then 401. **AC-6: Auth claim chain proven (MFA step-up)** Given the requesting access token has `amr=["pwd"]` only (no MFA) When `POST /sessions/mission` is called (after AZ-534 ships) Then 403 with detail `"mission tokens require step-up MFA"`. Until AZ-534 ships, AC-6 is enforced as a TODO comment in code; do not block this ticket on AZ-534. ## Blackbox Tests | AC Ref | Initial Data/Conditions | What to Test | Expected Behavior | NFR References | |--------|------------------------|-------------|-------------------|----------------| | AC-1 | Pilot session, 9h request | POST /sessions/mission | exp ≈ now+10h, no refresh, class=mission | — | | AC-2 | 15h request | POST /sessions/mission | 400 with cap message | — | | AC-3 | Mission token from AC-1 | Decode claims | mission_id, aircraft_id, aud, sid, jti present | — | | AC-4 | Open mission for UAV-117 | UAV-117 calls /token/refresh | Mission revoked, token dead | — | | AC-5 | No auth header | POST /sessions/mission | 401 | — | | AC-6 | amr=["pwd"] token (post-AZ-534) | POST /sessions/mission | 403 step-up required | — | ## Risks / Notes - Long-lived tokens are dangerous if leaked. Hardware binding is the right long-term answer; document this as known-risk in `_docs/05_security/security_report.md`. - The `valid_region` bbox is informational until satellite-provider enforces it. Document the planned enforcement in the cross-workspace coordination note.