hcengineering/platform · error
Unauthorized
Error message
Unauthorized
What it means
The backup API's handleBackup authenticates requests via a token taken from the Authorization header or, failing that, the 'presentation-metadata-Token' cookie. If neither yields a non-empty string token, it responds HTTP 401 with body 'Unauthorized' and aborts the backup operation.
Source
Thrown at services/backup/backup-api-pod/src/server.ts:162
},
15 * 60 * 1000
)
async function handleBackup (request: Request<any>, res: Response<any>): Promise<void> {
const headers = request.headers
const workspace = request.params.workspace ?? ''
const file: string | undefined = request.params.file
const authHeader = headers.authorization ?? ''
let token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : undefined
if (token == null) {
const cookies = (headers.cookie ?? '').split(';').map((it) => it.trim().split('='))
token = cookies.find((it) => it[0] === 'presentation-metadata-Token')?.[1]
}
if (token === undefined || typeof token !== 'string' || token === '') {
res.status(401).end('Unauthorized')
return
}
let workspaceId: WorkspaceUuid | undefined
let isAdmin: boolean = false
try {
const decoded = decodeTokenVerbose(ctx, token)
if (decoded === undefined) {
res.status(401).end('Unauthorized')
return
}
workspaceId = decoded.workspace
isAdmin = decoded.extra?.admin === 'true'
} catch (err: any) {
res.status(401).end('Unauthorized')
return
}View on GitHub (pinned to 63e28dc964)
Solutions
- Send the token in the Authorization header (preferred): Authorization: Bearer <token>.
- Ensure the 'presentation-metadata-Token' cookie is set and non-empty when relying on browser sessions.
- Re-authenticate to obtain a fresh token if the session expired.
- Confirm cookie name/domain/sameSite settings allow the cookie to reach the backup API.
Example fix
// before
fetch(BACKUP_URL, { method: 'POST' })
// after
fetch(BACKUP_URL, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` }
}) Defensive patterns
Strategy: validation
Validate before calling
function hasBackupCredentials(init: RequestInit & { token?: string }): boolean {
const h = new Headers(init.headers)
return h.has('authorization') || typeof init.token === 'string' && init.token.length > 0
} Type guard
function hasValidToken(token: unknown): token is string {
return typeof token === 'string' && token.length > 0
} Try / catch
const res = await fetch(BACKUP_URL, { headers: { Authorization: `Bearer ${token}` } })
if (res.status === 401) {
token = await reauthenticate() // refresh session/token then retry once
return fetch(BACKUP_URL, { headers: { Authorization: `Bearer ${token}` } })
} Prevention
- Always send the Authorization header explicitly when scripting backup calls.
- Check cookie presence ('presentation-metadata-Token') before relying on browser-session auth.
- Re-authenticate on session expiry instead of retrying with stale cookies.
- Verify cookie sameSite/domain settings when calling the backup API cross-origin.
When it happens
Trigger: Request to the backup endpoint with no Authorization header and no 'presentation-metadata-Token' cookie, or with an empty/non-string cookie value.
Common situations: Calling the backup API from scripts/curl without session cookies; browser sessions where the cookie expired or was cleared; cookie name changed across versions; same-site cookie policies blocking the cookie.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/7055287af2702e76.
Report an issue: GitHub.