Reference
Authentication
One personal access token, sent as a bearer. There are no keys, no secrets to pair, and no OAuth dance. A token is a sign-in, and it acts as the person who made it.
The header
Every request carries it. Nothing else authenticates: there is no query parameter and no body field, because either would end up in a log.
Authorization: Bearer mp_YOUR_MORSE_TOKENGET/auth/me
curl -H "Authorization: Bearer $MORSE_TOKEN" \
"https://onmorse.com/api/auth/me"What a token looks like
The same shape GitHub uses, and for the same two reasons. The prefix is what lets a secret scanner spot one in a commit. The checksum is what lets Morse refuse a forged or mistyped token without touching the database, which is the only defence against a flood of junk while there is no rate limit.
mp_4Xn2qK…8wZa7Bq2F
- mp_
- Prefix
- What a secret scanner matches on
- 4Xn2qK…8wZ
- 40 random characters
- Base62, about 238 bits
- a7Bq2F
- Checksum
- CRC32 of the 40, in base62
49 characters
Making one
In the app, at Settings → API tokens. Never through the API; see the 403 below for why.
- Name it after whatever will use it, up to 60 characters. It is how you will recognise it later.
- Choose an expiry: 30, 60 or 90 days, a day you pick, or never.
- Copy it. This is the only time it exists anywhere but your clipboard.
Twenty live tokens per person. Revoke one to make room rather than asking for the cap to move.
What Morse keeps
A SHA-256 of the token and its last four characters. That is all: the last four are for recognising a row in the list, and the hash is for matching a request. Nothing stored can be turned back into a token, so a lost one is lost and a leaked database yields nothing to replay.
The 40 random characters carry about 238 bits, so there is nothing to precompute against and a slow password hash would add cost to every request while protecting nothing.
When one stops working
Three states, worked out per request rather than stored.
| State | Meaning | What you get |
|---|---|---|
| active | Not revoked, not past its date, used within the year. | Works. |
| expired | Past the expiry you chose, or unused for 365 days. | 401. Make a new one; there is no renewing. |
| revoked | You revoked it, or an admin disabled the account. | 401, from the next request. |
Idleness is the one that surprises people: a token unused for 365 days is finished, whatever its expiry said. Morse stamps last_used_at at most once a minute, so reading through a token does not turn every request into a write.
Refusals
A 401 carries WWW-Authenticate: Bearer, naming the scheme that would have worked. Browsers ignore a bearer challenge, so it never raises a sign-in prompt.
| Status | detail | What happened |
|---|---|---|
| 401 | This token has expired. Create a new one in Settings. | The token is real and past its date. |
| 401 | Not authenticated. | Missing, malformed, checksum failed, revoked, idle a year, or the account is disabled. One answer for all of them, deliberately. |
| 403 | Manage tokens in the Morse app. | A token reached a token-management route. |
The second row collapses several causes on purpose. A prober must not be able to tell a revoked token from a malformed one, or learn that an account exists; the distinction lives only in Morse’s own logs.
The 403 is narrow: tokens cannot list, make or revoke tokens. A token that could make tokens could quietly mint itself a spare before you revoked it, so that one door is shut to them and open only to a signed-in browser.
Tokens and sessions
The API accepts two ways in: this token, and the browser session cookie the app itself uses. Both are declared as security schemes, which is why the Authorize button in the OpenAPI explorer offers both.
- The token wins. A request carrying both is a script run by someone who also happens to be signed in, and the token is the identity that request chose.
- A token never renews a session. Sessions slide forward as you use the app; a token request leaves that alone, because the session is not what it signed in with.
- Signing out does not touch tokens. Clearing a browser’s cookie ends that browser, nothing else. Revoke in Settings.