Start with the Setup Guide if you still need to wire
createAggClient,
AggProvider, or AggAuthProvider. After sign-in, see Token Refresh
for session renewal, Account Linking for adding additional
providers, and Partner External ID Linking if you need to attach
your own internal user ID to the AGG profile.siwefor Ethereum walletssiwsfor Solana walletsgooglefor Google OAuthtwitterfor X OAuthapplefor Apple Sign Inemailfor magic-link email sign-inprivyfor Privy access tokens — requires the Privy Wallet Provider
How It Works
Most flows start withclient.authStart(...) and complete in one of two ways. Privy skips
authStart entirely, because your app already holds a Privy access token:
Bot Protection (challenge_required)
For wallet (siwe, siws) and email (email) providers, AGG enforces an optional Cloudflare
Turnstile challenge when your app crosses its configured user-count threshold. OAuth providers
(google, twitter, apple) and privy bypass this layer — the identity provider is already the
human proof.
When the challenge is active, POST /auth/start may return an extra response type:
- Call
client.authStart({ provider: "siwe" }). - If the response type is
challenge_required, render the Cloudflare Turnstile widget using the returnedsiteKey. - On solve, resubmit
client.authStart({ provider: "siwe", turnstileToken: "<cf-token>" }). - The server verifies the token against its linked widget and returns the normal
nonce/magic_linkresponse. - An invalid or reused token returns
403— retry with a fresh Turnstile solve.
SDK: SIWE
SIWE is fully client-side and does not redirect.HttpOnly cookie instead, configure the SDK
client with authDelivery: "cookie-refresh". In that mode, verify() and exchangeAuthCode()
may omit refreshToken from the JSON response. That cookie stays on the AGG API host and is scoped
to /auth routes.
SDK: SIWS
SIWS is also fully client-side and does not redirect.SIWS with Ledger
Ledger is not a separate AGG auth flow. It uses the exact same SIWS contract:client.authStart({ provider: "siws" })client.buildSiwsMessage(...)wallet.signMessage(new TextEncoder().encode(message))bs58.encode(signature)client.verify({ message, signature })
signMessage() but not Phantom’s wallet-specific signIn()
helper. AGG’s SIWS recipe stays Ledger-compatible by building the message in the dapp and signing
the raw UTF-8 bytes directly.
Privy
If your users already sign in with Privy, exchange their Privy access token for an AGG session. There is noauthStart, nonce, or redirect.
Privy sign-in is enabled by saving Privy as your wallet provider under Settings → Wallet
Provider — there is no Privy setting on the Auth tab. Tokens must come from that same Privy app.
See the Privy Wallet Provider guide for setup.
accessToken, refreshToken, and user as every other sign-in method,
so Token Refresh and the rest of this page apply unchanged. The Privy DID
becomes the user’s identity, so the same person signing in to two of your apps gets one identity
across both.
Signing in does not set up the user’s wallets — request their deposit addresses next, as described
in Initialize user wallets.
Every Privy sign-in failure is a 401:
SDK: Google / Twitter / Apple / Email
These four providers all use the same redirect-based flow.- Google
- Twitter
- Apple
- Email
React: recommended @agg-build/auth
Use @agg-build/auth when you want AGG’s connect/sign-in UI without forcing wallet dependencies
into @agg-build/ui. See the Connect Button reference for the
component surface shown below.
Mixed providers
Dedicated callback pages
If your app uses a standalone callback route, useuseAggAuthCallback():
React: useAggAuth
useAggAuth keeps the documented wallet ergonomics for custom UIs.
Ethereum
Solana
Token storage
Tokens are never written to storage. They live in process memory only. What the SDK persists tolocalStorage (keyed by appId) is a non-sensitive hint — the user’s id, username, avatar and
wallet address, plus a wasAuthenticated flag — used to bootstrap your UI without a flash of
signed-out state. Restoring that hint sets authStatus to "unknown", not "authenticated".
What survives a reload therefore depends on authDelivery:
Set
persistSession: false to opt out of the hint entirely.React Native
localStorage does not exist in React Native, so nothing is persisted and isAuthenticated flips
to false on every app restart. cookie-refresh is not the answer either — HttpOnly cookies don’t
survive there. Keep the default "body" mode, persist the refresh token yourself, and seed it back
on boot with setSession():
expo-secure-store or
react-native-keychain) — not AsyncStorage, which is unencrypted.
Installation matrix
Related
Setup Guide
Wire the base client, providers, and WebSocket connection first.
Token Refresh
Renew access tokens and recover gracefully from session expiry.
Account Linking
Connect additional OAuth providers to the current user profile.
User Notifications
Reuse the same session for authenticated WebSocket events.
Bot Protection
Render and verify Cloudflare Turnstile challenges during sign-in.
Privy Wallet Provider
Sign users in with Privy and trade from their existing Privy wallets.