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

# Mini App Authentication

> Complete guide to authentication flows and developer-branded signer creation in Farcaster mini apps

# Authentication & Signer Management

*Although titled "Mini app authentication", this can also be used in web apps if you'd like.*

## Overview

The authorization system is built around **signers** - cryptographic keys that allow your application to act on behalf of a user within the Farcaster protocol.

Full code for this flow can be found in the [Neynar Mini App Starter Kit](https://github.com/neynarxyz/create-farcaster-mini-app/blob/main/src/components/ui/NeynarAuthButton/index.tsx)

This authentication system is designed to work both in a regular web browser and inside a miniapp. In other words, it supports authentication when the miniapp context is not present (web browser) as well as when the app is running inside a miniapp. If you only need authentication for a web application, follow the Webapp flow; if you only need authentication inside a miniapp, follow the Miniapp flow.

## Architecture Components

The system involves four main components:

```mermaid theme={"system"}
sequenceDiagram
    participant A as Miniapp Client
    participant B as Miniapp Server
    participant C as Neynar Server
    participant D as Farcaster App

    A->>B: Step 1: Get Nonce
    B->>C: Fetch Nonce
    C-->>B: Return Nonce
    B-->>A: Return Nonce

    A->>A: Step 2: Inject nonce in SIWF message
    A->>D: Step 3: Ask user for signature
    D-->>A: Return signature

    A->>B: Step 4: Send SIWF message + signature
    B->>C: Step 5: Fetch Signers (/api/auth/signers)
    C-->>B: Return Signers

    B->>B: Step 6: Check if signer exists
    alt Signer not present
        B->>C: Step 7: Create Signer
        C-->>B: Return Signer

        B->>C: Step 8: Register Signed Key
        C-->>B: Return Approval URL
    end

    A->>A: Step 9: Start Polling
    A->>D: Step 10: Show QR (desktop) or Deep Link (mobile)
    D->>C: User Approves Signer
    A->>B: Poll for status
    B->>C: Check Approval Status
    C-->>B: Return Status
    B-->>A: Return Status

    alt Signer approved
        B->>C: Fetch Signers Again
        C-->>B: Return Signers
        A->>A: Step 11: Store in LocalStorage (webapp)
        B->>B: Store in Session (miniapp)
    end
```

### API Endpoints

| Endpoint                      | Method   | Purpose                       | Step             |
| ----------------------------- | -------- | ----------------------------- | ---------------- |
| `/api/auth/nonce`             | GET      | Generate authentication nonce | Step 1           |
| `/api/auth/signers`           | GET      | Fetch user signers            | Step 5           |
| `/api/auth/session-signers`   | GET      | Fetch signers with user data  | Step 5 (Miniapp) |
| `/api/auth/signer`            | POST     | Create new signer             | Step 7           |
| `/api/auth/signer`            | GET      | Check signer status           | Step 9           |
| `/api/auth/signer/signed_key` | POST     | Register signed key           | Step 8           |
| `/api/auth/[...nextauth]`     | GET/POST | NextAuth handlers             | Miniapp flow     |

## Complete Authentication Flow

### Step 1: Get the Nonce

The authentication process begins by fetching a cryptographic nonce from the Neynar server.

**Mini App Client → Mini App Server:**

```typescript theme={"system"}
const generateNonce = async () => {
  const response = await fetch('/api/auth/nonce');
  const data = await response.json();
  setNonce(data.nonce);
};
```

**Mini App Server → Neynar Server:**

```typescript theme={"system"}
// /api/auth/nonce/route.ts
export async function GET() {
  try {
    const client = getNeynarClient();
    const response = await client.fetchNonce();
    return NextResponse.json(response);
  } catch (error) {
    console.error('Error fetching nonce:', error);
    return NextResponse.json(
      { error: 'Failed to fetch nonce' },
      { status: 500 }
    );
  }
}
```

### Step 2: Inject Nonce in Sign in with Farcaster

The nonce is used to create a [Sign in with Farcaster](https://github.com/farcasterxyz/protocol/discussions/110) message.

```typescript theme={"system"}
// Webapp flow using Farcaster Auth Kit
const { signIn, connect, data } = useSignIn({
  nonce: nonce || undefined,
  onSuccess: onSuccessCallback,
  onError: onErrorCallback,
});

// Miniapp flow using Farcaster SDK
const handleMiniappSignIn = async () => {
  const result = await sdk.actions.signIn({ nonce });
  // result contains message and signature
};
```

### Step 3: Ask User for the Signature

The user is prompted to sign the SIWF message through their Farcaster client.

**Webapp flow:**

```typescript theme={"system"}
useEffect(() => {
  if (nonce && !useMiniappFlow) {
    connect(); // Triggers signing flow
  }
}, [nonce, connect, useMiniappFlow]);
```

**Miniapp flow:**

```typescript theme={"system"}
// User signs through Farcaster mobile app
const signInResult = await sdk.actions.signIn({ nonce });
const { message, signature } = signInResult;
```

### Step 4: Receive Message and Signature

Once the user signs the message, the client receives the signature.

```typescript theme={"system"}
const onSuccessCallback = useCallback(
  async (res: UseSignInData) => {
    console.log('✅ Authentication successful:', res);
    setMessage(res.message);
    setSignature(res.signature);
  },
  [useMiniappFlow, fetchUserData]
);
```

### Step 5: Send to /api/auth/signers to Fetch Signers

With the signed message and signature, fetch existing signers for the user.

**Mini App Client → Mini App Server:**

```typescript theme={"system"}
const fetchAllSigners = async (message: string, signature: string) => {
  const endpoint = useMiniappFlow
    ? `/api/auth/session-signers?message=${encodeURIComponent(
        message
      )}&signature=${signature}`
    : `/api/auth/signers?message=${encodeURIComponent(
        message
      )}&signature=${signature}`;

  const response = await fetch(endpoint);
  const signerData = await response.json();
  return signerData;
};
```

**Mini App Server → Neynar Server:**

```typescript theme={"system"}
// /api/auth/signers/route.ts
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const message = searchParams.get('message');
  const signature = searchParams.get('signature');

  if (!message || !signature) {
    return NextResponse.json(
      { error: 'Message and signature are required' },
      { status: 400 }
    );
  }

  try {
    const client = getNeynarClient();
    const data = await client.fetchSigners({ message, signature });
    return NextResponse.json({
      signers: data.signers,
    });
  } catch (error) {
    console.error('Error fetching signers:', error);
    return NextResponse.json(
      { error: 'Failed to fetch signers' },
      { status: 500 }
    );
  }
}
```

**For Miniapp flow with user data:**

```typescript theme={"system"}
// /api/auth/session-signers/route.ts
export async function GET(request: Request) {
  try {
    const { searchParams } = new URL(request.url);
    const message = searchParams.get('message');
    const signature = searchParams.get('signature');

    const client = getNeynarClient();
    const data = await client.fetchSigners({ message, signature });
    const signers = data.signers;

    // Fetch user data if signers exist
    let user = null;
    if (signers && signers.length > 0 && signers[0].fid) {
      const {
        users: [fetchedUser],
      } = await client.fetchBulkUsers({
        fids: [signers[0].fid],
      });
      user = fetchedUser;
    }

    return NextResponse.json({
      signers,
      user,
    });
  } catch (error) {
    console.error('Error in session-signers API:', error);
    return NextResponse.json(
      { error: 'Failed to fetch signers' },
      { status: 500 }
    );
  }
}
```

### Step 6: Check if Signers are Present

Determine if the user has existing approved signers.

```typescript theme={"system"}
const hasApprovedSigners = signerData?.signers?.some(
  (signer: any) => signer.status === 'approved'
);

if (hasApprovedSigners) {
  // User has signers, proceed to store them
  proceedToStorage(signerData);
} else {
  // No signers, need to create new ones
  startSignerCreationFlow();
}
```

### Step 7: Create a Signer

If no signers exist, create a new signer.

**Mini App Client → Mini App Server:**

```typescript theme={"system"}
const createSigner = async () => {
  const response = await fetch('/api/auth/signer', {
    method: 'POST',
  });

  if (!response.ok) {
    throw new Error('Failed to create signer');
  }

  return await response.json();
};
```

**Mini App Server → Neynar Server:**

```typescript theme={"system"}
// /api/auth/signer/route.ts
export async function POST() {
  try {
    const neynarClient = getNeynarClient();
    const signer = await neynarClient.createSigner();
    return NextResponse.json(signer);
  } catch (error) {
    console.error('Error creating signer:', error);
    return NextResponse.json(
      { error: 'Failed to create signer' },
      { status: 500 }
    );
  }
}
```

### Step 8: Register a Signed Key

Register the signer's public key with the Farcaster protocol.

**Mini App Client → Mini App Server:**

```typescript theme={"system"}
const generateSignedKeyRequest = async (
  signerUuid: string,
  publicKey: string
) => {
  const response = await fetch('/api/auth/signer/signed_key', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      signerUuid,
      publicKey,
      redirectUrl: window.location.origin, // Optional redirect after approval
    }),
  });

  if (!response.ok) {
    throw new Error('Failed to register signed key');
  }

  return await response.json();
};
```

**Mini App Server → Neynar Server:**

```typescript theme={"system"}
// /api/auth/signer/signed_key/route.ts
export async function POST(request: Request) {
  const body = await request.json();
  const { signerUuid, publicKey, redirectUrl } = body;

  // Validate required fields
  if (!signerUuid || !publicKey) {
    return NextResponse.json(
      { error: 'signerUuid and publicKey are required' },
      { status: 400 }
    );
  }

  try {
    // Get the app's account from seed phrase
    const seedPhrase = process.env.SEED_PHRASE;
    const shouldSponsor = process.env.SPONSOR_SIGNER === 'true';

    if (!seedPhrase) {
      return NextResponse.json(
        { error: 'App configuration missing (SEED_PHRASE)' },
        { status: 500 }
      );
    }

    const neynarClient = getNeynarClient();
    const account = mnemonicToAccount(seedPhrase);

    // Get app FID from custody address
    const {
      user: { fid },
    } = await neynarClient.lookupUserByCustodyAddress({
      custodyAddress: account.address,
    });

    const appFid = fid;

    // Generate deadline (24 hours from now)
    const deadline = Math.floor(Date.now() / 1000) + 86400;

    // Generate EIP-712 signature
    const signature = await account.signTypedData({
      domain: SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN,
      types: {
        SignedKeyRequest: SIGNED_KEY_REQUEST_TYPE,
      },
      primaryType: 'SignedKeyRequest',
      message: {
        requestFid: BigInt(appFid),
        key: publicKey,
        deadline: BigInt(deadline),
      },
    });

    const signer = await neynarClient.registerSignedKey({
      appFid,
      deadline,
      signature,
      signerUuid,
      ...(redirectUrl && { redirectUrl }),
      ...(shouldSponsor && { sponsor: { sponsored_by_neynar: true } }),
    });

    return NextResponse.json(signer);
  } catch (error) {
    console.error('Error registering signed key:', error);
    return NextResponse.json(
      { error: 'Failed to register signed key' },
      { status: 500 }
    );
  }
}
```

### Step 9: Start Polling

Begin polling the signer status to detect when it's approved.

```typescript theme={"system"}
const startPolling = (
  signerUuid: string,
  message: string,
  signature: string
) => {
  const interval = setInterval(async () => {
    try {
      const response = await fetch(`/api/auth/signer?signerUuid=${signerUuid}`);
      const signerData = await response.json();

      if (signerData.status === 'approved') {
        clearInterval(interval);
        console.log('✅ Signer approved!');
        // Refetch all signers
        await fetchAllSigners(message, signature);
      }
    } catch (error) {
      console.error('Error polling signer:', error);
    }
  }, 2000); // Poll every 2 seconds

  return interval;
};
```

**Polling API Implementation:**

```typescript theme={"system"}
// /api/auth/signer/route.ts (GET method)
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const signerUuid = searchParams.get('signerUuid');

  if (!signerUuid) {
    return NextResponse.json(
      { error: 'signerUuid is required' },
      { status: 400 }
    );
  }

  try {
    const neynarClient = getNeynarClient();
    const signer = await neynarClient.lookupSigner({
      signerUuid,
    });
    return NextResponse.json(signer);
  } catch (error) {
    console.error('Error fetching signer status:', error);
    return NextResponse.json(
      { error: 'Failed to fetch signer status' },
      { status: 500 }
    );
  }
}
```

### Step 10: Show Signer Approval URL

Display QR code for desktop users or deep link for mobile users.

```typescript theme={"system"}
const handleSignerApproval = (approvalUrl: string) => {
  const isMobile =
    /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
      navigator.userAgent
    );

  if (isMobile && context?.client) {
    // Mobile: Deep link to Farcaster app
    const farcasterUrl = approvalUrl.replace(
      'https://client.farcaster.xyz/deeplinks/signed-key-request',
      'https://farcaster.xyz/~/connect'
    );

    // Use SDK to open URL in Farcaster app
    sdk.actions.openUrl(farcasterUrl);
  } else {
    // Desktop: Show QR code
    setSignerApprovalUrl(approvalUrl);
    setDialogStep('access');
    setShowDialog(true);
  }
};
```

### Step 11: Store Signers

Once approved, store the signers in appropriate storage.

**Webapp flow (LocalStorage):**

```typescript theme={"system"}
const storeInLocalStorage = (user: any, signers: any[]) => {
  const authState = {
    isAuthenticated: true,
    user,
    signers,
  };

  setItem(STORAGE_KEY, authState);
  setStoredAuth(authState);
};
```

**Miniapp flow (NextAuth Session):**

```typescript theme={"system"}
const updateSessionWithSigners = async (signers: any[], user: any) => {
  if (useMiniappFlow && message && signature && nonce) {
    const signInData = {
      message,
      signature,
      nonce,
      fid: user?.fid?.toString(),
      signers: JSON.stringify(signers),
      user: JSON.stringify(user),
    };

    const result = await backendSignIn('neynar', {
      ...signInData,
      redirect: false,
    });

    if (result?.ok) {
      console.log('✅ Session updated with signers');
    }
  }
};
```

## State Management & Storage

### Webapp flow State (LocalStorage)

```typescript theme={"system"}
interface StoredAuthState {
  isAuthenticated: boolean;
  user: {
    fid: number;
    username: string;
    display_name: string;
    pfp_url: string;
    custody_address: string;
    profile: {
      bio: { text: string };
      location?: any;
    };
    follower_count: number;
    following_count: number;
    verifications: string[];
    verified_addresses: {
      eth_addresses: string[];
      sol_addresses: string[];
      primary: {
        eth_address: string;
        sol_address: string;
      };
    };
    power_badge: boolean;
    score: number;
  } | null;
  signers: {
    object: 'signer';
    signer_uuid: string;
    public_key: string;
    status: 'approved';
    fid: number;
  }[];
}

// Stored in localStorage with key 'neynar_authenticated_user'
const STORAGE_KEY = 'neynar_authenticated_user';
```

### Miniapp flow State (NextAuth Session)

```typescript theme={"system"}
interface Session {
  provider: 'neynar';
  user: {
    fid: number;
    object: 'user';
    username: string;
    display_name: string;
    pfp_url: string;
    custody_address: string;
    profile: {
      bio: { text: string };
      location?: any;
    };
    follower_count: number;
    following_count: number;
    verifications: string[];
    verified_addresses: {
      eth_addresses: string[];
      sol_addresses: string[];
      primary: {
        eth_address: string;
        sol_address: string;
      };
    };
    power_badge: boolean;
    score: number;
  };
  signers: {
    object: 'signer';
    signer_uuid: string;
    public_key: string;
    status: 'approved';
    fid: number;
  }[];
}
```

## Security & Configuration

### EIP-712 Signature Validation

```typescript theme={"system"}
// From /lib/constants.ts
export const SIGNED_KEY_REQUEST_VALIDATOR_EIP_712_DOMAIN = {
  name: 'Farcaster SignedKeyRequestValidator',
  version: '1',
  chainId: 10,
  verifyingContract:
    '0x00000000fc700472606ed4fa22623acf62c60553' as `0x${string}`,
};

export const SIGNED_KEY_REQUEST_TYPE = [
  { name: 'requestFid', type: 'uint256' },
  { name: 'key', type: 'bytes' },
  { name: 'deadline', type: 'uint256' },
];
```

### Required Environment Variables

```bash theme={"system"}
# Neynar API configuration
NEYNAR_API_KEY=your_neynar_api_key
NEYNAR_CLIENT_ID=your_neynar_client_id

# App signing key for signer registration
SEED_PHRASE=your_twelve_word_mnemonic_phrase

# Optional: Sponsor signer creation costs (recommended for production)
SPONSOR_SIGNER=true
```

### Flow Detection

```typescript theme={"system"}
// Determine which flow to use
const { context } = useMiniApp();
const useMiniappFlow = context !== undefined;

// Webapp flow uses localStorage + Farcaster Auth Kit
// Miniapp flow uses NextAuth sessions + Farcaster SDK
```

### Integration Examples

#### Checking Authentication Status

```typescript theme={"system"}
// Webapp flow
const isAuthenticated =
  storedAuth?.isAuthenticated &&
  storedAuth?.signers?.some((s) => s.status === 'approved');

// Miniapp flow
const isAuthenticated =
  session?.provider === 'neynar' &&
  session?.user?.fid &&
  session?.signers?.some((s) => s.status === 'approved');
```

#### Using Signers for Farcaster Actions

```typescript theme={"system"}
// Get signer
const signer = (useMiniappFlow ? session?.signers : storedAuth?.signers)[0];

if (signer) {
  // Use signer for publishing casts
  const client = getNeynarClient();
  await client.publishCast({
    signerUuid: signer.signer_uuid,
    text: 'Hello from my mini app!',
  });
}
```

## Summary

Authentication flow provides a comprehensive system for:

1. **Secure Authentication**: Using SIWF protocol with cryptographic nonces
2. **Signer Management**: Creating and approving signers for Farcaster actions
3. **Multi-Platform Support**: Works in web browsers and Farcaster mobile clients
4. **State Persistence**: Maintains authentication across sessions

The system abstracts the complexity of Farcaster protocol interactions while providing a seamless user experience for both web and mobile environments.

[Feel Free to reach out with any questions or for further assistance in integrating this authentication flow into your Farcaster mini app!](https://neynar.com/slack)

## Happy coding! 🎉
