drift Docs
Start
What is Drift?
The tour, if you are new here.
Why Drift?
The case for a smaller cloud.
Getting started
Nothing to deployed, in one command.
Architecture
How a slice is put together.
What it costs
The free grant, five unit prices, two rules.
Build
Canvas
Static sites, same origin as your API.
Tools
Operate
Auth
Accounts, tokens and scopes.
Security
Boundaries, sandboxing and hardening.

Deed

drift.Deed.{KeyAuth,JWT,Vault,Link,Pocket}: identity, a peer namespace to Backbone rather than one of its primitives. The Deed guide explains what each one is for; this is the call surface.

KeyAuth

Go
// passwordless Ed25519 device-key auth (uid = the public key)
Challenge(pubkey string) (string, error)              // → one-time nonce
Verify(pubkey, sig, domain string) (string, error)   // checks sig over {domain,nonce,pubkey} → THIS slice's JWT

The nonce lives 120 seconds and is burned when it verifies. The signature is over one exact byte string (compact JSON, sorted keys, no whitespace) and a mismatch of one byte comes back as 401 bad signature with no further detail.

JWT

Go
// general-purpose HS256 sign/verify; KeyAuth mints its tokens through this
Issue(claims JWTClaims)               (string, error)     // Exp REQUIRED; Iat/Iss/Jti auto-set if zero
Verify(token string, opts JWTVerifyOptions) (JWTClaims, error) // pass JWTVerifyOptions{}, NOT nil
SliceID()                            string              // this slice's issuer string, "" on error

type JWTClaims struct {
    Sub    string
    Iat    int64
    Exp    int64
    Nbf    int64
    Iss    string
    Aud    []string
    Jti    string
    Custom map[string]any   // your app claims live HERE, e.g. claims.Custom["role"]
}
type JWTVerifyOptions struct { Audience, AllowedIssuer string }

Three things bite if you guess.

Exp is required on Issue (a missing or past expiry errors); app-defined fields go in Custom, a map, not at the top level; and Verify enforces the issuer is your slice by default, so tokens minted elsewhere fail unless you set AllowedIssuer.

A failure carries one of nine stable reasons: malformed, bad_signature, expired, not_yet_valid, wrong_algorithm, wrong_issuer, wrong_audience, invalid_claims, missing_exp. Branch on that, not on the message text.

Vault

Go
// zero-knowledge recovery store (encrypt client-side before Put)
Put(uid string, blob any) error   ·   Get(uid string) (json.RawMessage, error)   // latest blob

Vault does not authenticate its caller.

uid is a plain argument, so any function in your slice can read or write any uid's blob. The confidentiality guarantee rests entirely on the client having encrypted first, so check who is asking before you hand a blob back. Pocket is the primitive that enforces per-identity isolation for you.
Go
// multi-device continuity (sig is computed client-side, never by this SDK)
Begin(pubkey string, metadata ...string) (string, error)                     // → session id
SessionInfo(sessionID string) (LinkSessionInfo, error)                       // {NewPubkey, Metadata}
Attest(identity, sessionID, attestingPubkey, sig string, sealed ...string) error
Complete(sessionID string) (LinkStatus, error)                              // {Status, Identity, Sealed}
Revoke(identity, targetPubkey, revokingPubkey, sig string) error
QR(text string) (string, error)                                            // → inline SVG markup

The two trailing variadics carry opaque strings Deed relays without interpreting. Begin's metadata is whatever the joining device wants an attesting device to see, typically an ephemeral public key, retrieved with SessionInfo. Attest's sealed is a payload encrypted for that key, handed back by Complete once the session reports "attested". Rust exposes them as separate functions, begin_with_metadata and attest_with_seal, since it has no variadics.

Pocket

Go
// E2EE per-identity app data; every call takes the bearer token explicitly
Set(token, key string, blob any) error   ·   Get(token, key string) (json.RawMessage, error)
Delete(token, key string) error          ·   List(token string) ([]string, error)

Pocket is the one Deed primitive that authenticates its caller: the token is the one KeyAuth.Verify returned, and its identity is the only one a call can read or write under. Passing the token explicitly rather than holding hidden session state is deliberate: the authority for a call is visible at the call site.

None of this works on your laptop.

Every Deed call under drift atomic run errors with deed requires a running slice (DEED_URL). That is deliberate, because a silent no-op would report success while storing nothing. Structure a function so its Deed calls sit behind one seam and the rest stays testable locally; see what runs locally.

Authentication walks the full login→verify flow end to end.