> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agg.market/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Sign users in with SIWE, SIWS (Solana), Google, Twitter, Apple, or Email magic link

<Info>
  Start with the [Setup Guide](/api/setup) if you still need to wire `createAggClient`,
  `AggProvider`, or `AggAuthProvider`. After sign-in, see [Token Refresh](/recipes/token-refresh)
  for session renewal, [Account Linking](/recipes/account-linking) for adding additional
  providers, and [Partner External ID Linking](/recipes/external-id-linking) if you need to attach
  your own internal user ID to the AGG profile.
</Info>

AGG supports six authentication providers:

* `siwe` for Ethereum wallets
* `siws` for Solana wallets
* `google` for Google OAuth
* `twitter` for X OAuth
* `apple` for Apple Sign In
* `email` for magic-link email sign-in

## How It Works

All flows start with `client.authStart(...)`, but they complete in two different ways:

| Flow                  | Providers                             | Completion                                                                 |
| --------------------- | ------------------------------------- | -------------------------------------------------------------------------- |
| Message signing       | `siwe`, `siws`                        | Call `client.verify({ message, signature })` to receive tokens directly.   |
| Redirect / magic link | `google`, `twitter`, `apple`, `email` | AGG redirects back with `?code=...`; call `client.exchangeAuthCode(code)`. |

## 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`) 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:

```json theme={null}
{ "type": "challenge_required", "siteKey": "0x4AAAAAAA..." }
```

Your flow becomes:

1. Call `client.authStart({ provider: "siwe" })`.
2. If the response type is `challenge_required`, render the Cloudflare Turnstile widget using the
   returned `siteKey`.
3. On solve, resubmit `client.authStart({ provider: "siwe", turnstileToken: "<cf-token>" })`.
4. The server verifies the token against its linked widget and returns the normal `nonce` /
   `magic_link` response.
5. An invalid or reused token returns `403` — retry with a fresh Turnstile solve.

```typescript theme={null}
const start = await client.authStart({ provider: "siwe" });

if (start.type === "challenge_required") {
  const turnstileToken = await renderTurnstileWidget(start.siteKey); // your UI helper
  const retried = await client.authStart({ provider: "siwe", turnstileToken });
  // retried.type === "nonce" — continue the SIWE flow below
}
```

## SDK: SIWE

SIWE is fully client-side and does not redirect.

```typescript theme={null}
import { createAggClient } from "@agg-build/sdk";

const client = createAggClient({
  baseUrl: "https://api.agg.market",
  appId: "your-app-id",
});

const { nonce } = await client.authStart({ provider: "siwe" });

const message = client.buildSiweMessage({
  address: walletAddress,
  chainId: 1,
  nonce,
  domain: window.location.host,
  uri: window.location.origin,
});

const signature = await signMessage({ message }); // wagmi, viem, ethers, etc.
const { accessToken, refreshToken, user } = await client.verify({ message, signature });
```

If your browser app wants the refresh token set as an `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.

```typescript theme={null}
import bs58 from "bs58";
import { createAggClient } from "@agg-build/sdk";

const client = createAggClient({
  baseUrl: "https://api.agg.market",
  appId: "your-app-id",
});

const { nonce } = await client.authStart({ provider: "siws" });

const message = client.buildSiwsMessage({
  address: walletPublicKey.toBase58(),
  chainId: "mainnet",
  nonce,
  domain: window.location.host,
  uri: window.location.origin,
});

const signedBytes = await wallet.signMessage(new TextEncoder().encode(message));
const signature = bs58.encode(signedBytes);
const { accessToken, refreshToken, user } = await client.verify({ message, signature });
```

## SIWS with Ledger

Ledger is not a separate AGG auth flow. It uses the exact same SIWS contract:

1. `client.authStart({ provider: "siws" })`
2. `client.buildSiwsMessage(...)`
3. `wallet.signMessage(new TextEncoder().encode(message))`
4. `bs58.encode(signature)`
5. `client.verify({ message, signature })`

This matters because Ledger supports `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.

## SDK: Google / Twitter / Apple / Email

These four providers all use the same redirect-based flow.

<Tabs>
  <Tab title="Google">
    ```typescript theme={null}
    const response = await client.authStart({
      provider: "google",
      redirectUrl: "https://yourapp.com/auth/callback",
    });

    if (response.type === "redirect") {
      window.location.assign(response.url);
    }
    ```
  </Tab>

  <Tab title="Twitter">
    ```typescript theme={null}
    const response = await client.authStart({
      provider: "twitter",
      redirectUrl: "https://yourapp.com/auth/callback",
    });

    if (response.type === "redirect") {
      window.location.assign(response.url);
    }
    ```
  </Tab>

  <Tab title="Apple">
    ```typescript theme={null}
    const response = await client.authStart({
      provider: "apple",
      redirectUrl: "https://yourapp.com/auth/callback",
    });

    if (response.type === "redirect") {
      window.location.assign(response.url);
    }
    ```
  </Tab>

  <Tab title="Email">
    ```typescript theme={null}
    await client.authStart({
      provider: "email",
      email: "user@example.com",
      redirectUrl: "https://yourapp.com/auth/callback",
    });
    ```
  </Tab>
</Tabs>

After the user authenticates, AGG redirects back with a one-time auth code:

```text theme={null}
https://yourapp.com/auth/callback?code=a1b2c3d4...
```

Exchange that code for tokens on the redirect target page:

```typescript theme={null}
async function handleAuthRedirect() {
  const params = new URLSearchParams(window.location.search);
  const code = params.get("code");
  if (!code) return;

  const { accessToken, refreshToken, user } = await client.exchangeAuthCode(code);

  window.history.replaceState(null, "", window.location.pathname);
}

handleAuthRedirect();
```

The auth code is single-use and expires quickly, so exchange it immediately.

## 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](/components/auth/connect-button) for the
component surface shown below.

### Mixed providers

```tsx theme={null}
import { AggProvider } from "@agg-build/hooks";
import { createAggClient } from "@agg-build/sdk";
import {
  AggAuthProvider,
  ConnectButton,
  createEmailAuthMethod,
  createGoogleAuthMethod,
} from "@agg-build/auth";
import { useSiweAuthMethod } from "@agg-build/auth/siwe";
import { WagmiProvider } from "wagmi";
import { wagmiConfig } from "./wagmi-config";

const client = createAggClient({
  baseUrl: "https://api.agg.market",
  appId: "your-app-id",
});

function AuthButton() {
  const siwe = useSiweAuthMethod({
    statement: "Sign in to AGG",
  });

  return (
    <AggAuthProvider methods={[siwe, createGoogleAuthMethod(), createEmailAuthMethod()]}>
      <ConnectButton />
    </AggAuthProvider>
  );
}

export function App() {
  return (
    <WagmiProvider config={wagmiConfig}>
      <AggProvider client={client}>
        <AuthButton />
      </AggProvider>
    </WagmiProvider>
  );
}
```

Browse the live [Connect Button reference](/components/auth/connect-button).

### Dedicated callback pages

If your app uses a standalone callback route, use `useAggAuthCallback()`:

```tsx theme={null}
import { useAggAuthCallback } from "@agg-build/auth";

export function AuthCallbackPage() {
  const { error, isHandled, isHandling, user } = useAggAuthCallback();

  if (isHandling) return <p>Finishing sign-in...</p>;
  if (error) return <p>{error.message}</p>;
  if (isHandled) return <p>Signed in as {user?.id}</p>;

  return <p>No AGG auth callback data was found.</p>;
}
```

## React: `useAggAuth`

`useAggAuth` keeps the documented wallet ergonomics for custom UIs.

### Ethereum

```tsx theme={null}
import { useAggAuth } from "@agg-build/hooks";
import { useAccount, useSignMessage } from "wagmi";

function SignIn() {
  const { address, chainId } = useAccount();
  const { signMessageAsync } = useSignMessage();
  const { signIn, signOut, isAuthenticated, user, isLoading } = useAggAuth({
    address,
    chainId,
    signMessage: async (message) => signMessageAsync({ message }),
  });

  if (isAuthenticated) {
    return (
      <div>
        <p>Signed in as {user.id}</p>
        <button onClick={signOut}>Sign Out</button>
      </div>
    );
  }

  return (
    <button onClick={() => signIn("Sign in to AGG")} disabled={isLoading}>
      Sign in with Ethereum
    </button>
  );
}
```

### Solana

```tsx theme={null}
import bs58 from "bs58";
import { useWallet } from "@solana/wallet-adapter-react";
import { useAggAuth } from "@agg-build/hooks";

function SolanaSignIn() {
  const { publicKey, signMessage } = useWallet();
  const { signIn, isLoading } = useAggAuth({
    address: publicKey?.toBase58(),
    chain: "solana",
    chainId: "mainnet",
    signMessage: async (message) => {
      const signedBytes = await signMessage!(new TextEncoder().encode(message));
      return bs58.encode(signedBytes);
    },
  });

  return (
    <button onClick={() => signIn("Sign in to AGG")} disabled={isLoading}>
      Sign in with Solana
    </button>
  );
}
```

## Token storage

The SDK automatically persists auth tokens in `localStorage`, keyed by `appId`, and restores them
when the client is constructed.

## Installation matrix

```bash theme={null}
# Base React packages
npm install @agg-build/sdk @agg-build/hooks @agg-build/ui @agg-build/auth

# Add only the providers you use
npm install wagmi
npm install @solana/wallet-adapter-react bs58
```

## Related

<CardGroup cols={2}>
  <Card title="Setup Guide" icon="gear" href="/api/setup">
    Wire the base client, providers, and WebSocket connection first.
  </Card>

  <Card title="Token Refresh" icon="key" href="/recipes/token-refresh">
    Renew access tokens and recover gracefully from session expiry.
  </Card>

  <Card title="Account Linking" icon="code" href="/recipes/account-linking">
    Connect additional OAuth providers to the current user profile.
  </Card>

  <Card title="User Notifications" icon="brackets-curly" href="/recipes/websocket-notifications">
    Reuse the same session for authenticated WebSocket events.
  </Card>

  <Card title="Bot Protection" icon="shield-halved" href="/recipes/bot-protection">
    Render and verify Cloudflare Turnstile challenges during sign-in.
  </Card>
</CardGroup>
