Per-grant token encryption
Per-grant access tokens are encrypted with keys derived from the grant secret, which TapAuth does not store. That means TapAuth cannot decrypt per-grant token blobs later, even though some service-connection refresh tokens remain server-accessible for reconnect and approval flows.
Your LLM Never Sees Your Credentials
TapAuth handles the OAuth dance — redirects, consent screens, token exchange — so your agent never touches your username, password, or OAuth client secrets. For per-request providers, the agent can receive exactly the requested scopes. For integration-level providers like Vercel and Notion, TapAuth narrows access by app tier (read-only vs read-write) rather than exact per-request scopes.
Every grant requires explicit browser-based approval. In most flows, that approval happens on the provider's OAuth screen; some flows add a follow-up TapAuth consent screen before the grant becomes active.
Zero-Knowledge Token Storage
Every grant is encrypted with its own X25519 sealed box — the same asymmetric encryption primitive used by libsodium and NaCl. Each grant gets a unique keypair derived from its secret, so compromising one grant reveals nothing about any other.
When you create a grant, TapAuth generates a cryptographically random secret (32 bytes of entropy via CSPRNG). From this single secret, we derive two independent values using HKDF-SHA256 (RFC 5869) with domain separation:
- •A verification hash for authenticating requests (stored in the database)
- •An X25519 private key seed that generates a keypair (only the public key is stored)
Per-grant access and refresh tokens are encrypted at callback time using the stored public key. Only someone with the grant secret can derive the private key needed to decrypt those per-grant blobs.
You Hold the Only Key
The grant secret is returned exactly once — when the grant is created. TapAuth never stores it, logs it, or transmits it again. The server only keeps a verification hash and a public key, neither of which can be used to decrypt per-grant tokens.
Different HKDF info tags (tapauth-verify-hash and tapauth-x25519-key) ensure cryptographic independence — knowing the verification hash reveals nothing about the private key.
If you lose your grant secret, there is no recovery for that grant. This is intentional. A “forgot my key” flow would mean TapAuth could access per-grant tokens, which defeats the purpose of this design.
Blast Radius of One
Every grant has its own keypair derived from its own secret. There is no master key that unlocks all grants. Compromising one grant secret exposes that grant's tokens and nothing else.
Revoked and expired grants are unrecoverable by design. When a grant is revoked or expires, the encrypted token data is purged from the database entirely — not just locked, but deleted.
When a grant receives its own refresh token, that refresh token is also encrypted with the same sealed box scheme. Some approval and reconnect flows additionally rely on separately stored service-connection refresh tokens protected by a server-held AES-GCM key.
Threat Model
What an attacker actually gets at each level of compromise.
| Attacker has… | What they get |
|---|---|
| Database access only | Encrypted blobs + public keys. Useless without grant secrets. |
| Server environment (ENV) | No grant secrets stored there. ENV alone does not decrypt per-grant blobs, but it does contain the key material used for service-connection tokens. |
| Database + ENV | Service-connection refresh tokens and the ability to mint fresh provider access. Per-grant ciphertext still cannot be decrypted without grant secrets. |
| One grant secret | That one grant's tokens. Nothing else. |
| Full server compromise (runtime memory) | Any in-flight decrypted grant tokens, plus service-connection tokens the server can decrypt and use. Per-grant ciphertext at rest still depends on grant secrets. |
Technical Details
Per-Grant Encryption
X25519 sealed box (NaCl/libsodium). Ephemeral ECDH key agreement + XSalsa20-Poly1305 authenticated encryption.
Key Derivation
HKDF-SHA256 (RFC 5869) with domain-separated info tags for cryptographic independence between verification and encryption.
Secret Generation
CSPRNG, 32 random bytes mapped into a base62 alphabet with gs_ prefix. Returned once at grant creation.
Secret Verification
Timing-safe comparison via crypto.timingSafeEqual to prevent side-channel attacks.
Refresh Tokens
Per-grant refresh tokens, when present, are also sealed-box encrypted. Separately stored service-connection refresh tokens use server-held AES-GCM so TapAuth can complete reconnect and approval flows.
Human-in-the-Loop
Every grant requires explicit browser approval. OAuth approval is registration, and some flows add a follow-up TapAuth consent step before activation.
Ready to get started?
Read the docs or try TapAuth today. Per-grant token decryption stays in your hands, while the threat model stays explicit.