TypeScript SDK — Authentication
The SDK supports three authentication strategies. Pick the one that matches your application:
| Strategy | Import | Use when |
|---|---|---|
| HMAC | @gemini-markets/sdk/server | Server-side apps with API key + secret |
| OAuth (confidential) | @gemini-markets/sdk/server | Server-side apps acting on behalf of users |
| OAuth (public/PKCE) | @gemini-markets/sdk/browser | Browser and mobile apps — no client secret |
HMAC authentication
HMAC is the simplest path for server-to-server integration. Every request is signed with your API secret using HMAC-SHA384.
Code
The SDK handles payload encoding, nonce generation, and signature computation automatically. You never construct headers manually.
Nonce modes
Every authenticated request includes a nonce to prevent replay attacks. The SDK supports two modes:
Code
Use "time-based" if the sandbox returns InvalidNonce errors with the default mode.
How HMAC signing works
For reference, this is what the SDK does on every authenticated request:
- Build a JSON payload with the
requestpath and anonce - Base64-encode the payload →
X-GEMINI-PAYLOADheader - HMAC-SHA384 sign the base64 string with your secret →
X-GEMINI-SIGNATUREheader - Send the API key in
X-GEMINI-APIKEY
See the API key authentication docs for the full protocol specification.
OAuth — confidential server client
Use OAuthAuth when your server acts on behalf of a user who has authorized your application. Confidential clients have a client_secret.
Code
Authorization flow
OAuth requires a multi-step flow: redirect the user, receive a callback, exchange the code for tokens.
Code
Implementing a token store
The SDK does not persist tokens — you provide a tokenStore that handles storage. The store must implement load, save, clear, consumeAuthorizationState, and runExclusive for shared locking and one-shot authorization-code exchanges:
Code
The lock must cover every OAuthAuth instance and process sharing the store. Implement it with a distributed lock (e.g., Redis or a database row lock) when the store is shared across processes, so concurrent single-use refresh token rotations and authorization-code exchanges cannot race. Implement consumeAuthorizationState as a durable atomic claim with a short expiry (for example, a database insert with a unique state key and a ten-minute TTL) whenever authorization transactions can cross page or process boundaries. The method must return false for a state that has already been claimed.
A minimal in-memory implementation for development:
Code
Token refresh
Access tokens expire after 24 hours. The SDK refreshes them automatically when credentialHeaders() detects an expired token. Refresh happens inside the store's runExclusive lock to prevent concurrent rotation of single-use refresh tokens.
You can tune refresh timing:
Code
Revocation
Revoke tokens explicitly when a user disconnects your app:
Code
Browser OAuth (PKCE)
Browser apps cannot hold a client secret. Use BrowserOAuthAuth which enforces public-client PKCE at both the type and runtime levels — you cannot accidentally pass a confidential client.
Code
Code
The PKCE code challenge and verifier are generated automatically using Web Crypto (crypto.subtle). The verifier is sent during code exchange — no secret ever leaves the browser.
Browser OAuth authenticates REST requests only. It does not make private WebSocket streams or WebSocket order methods available in the browser: native browser WebSockets cannot send the required upgrade Authorization header, and the SDK rejects private WebSocket operations from the browser entry point. Use @gemini-markets/sdk/server or a server-side relay for authenticated WebSockets.
Scopes
Scopes control what the OAuth token can access. Request only what your app needs:
| Scope | Grants |
|---|---|
orders:create | Place and cancel orders |
orders:read | View orders and trade history |
balances:read | View account balances |
addresses:read | View approved withdrawal addresses |
history:read | View transaction history |
The full scope list is in the OAuth documentation.
Browser token persistence
In the browser, persist tokens to localStorage or sessionStorage:
Code
navigator.locks provides an origin-wide exclusive lock, so multiple tabs do not rotate the same
single-use refresh token or exchange the same authorization code concurrently. The state claim is
short-lived and is only for replay protection; persist the authorization transaction itself (including
the PKCE verifier) in sessionStorage as shown above. If the target browser does not support the Web Locks API,
use a compatible Web Locks polyfill or move token refresh into a service with a shared lock.
For production apps, consider encrypting tokens at rest and using sessionStorage for shorter-lived sessions.
What's next
- WebSocket — real-time streams with authenticated access
- Error handling — OAuth-specific error types and recovery