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

# Integration Guide

> How to securely integrate the Passkey UI using iframes and browser sessions.

The hosted UI provides a production-ready passkey interface that handles all WebAuthn complexity. This guide covers implementation for web and mobile applications.

<Note>
  The **appName** determines how the passkey will be named when created on your
  user's device. Use your application's name or a clear identifier so users can
  easily recognize it.
</Note>

### Web Integration (React/Next.js) - Hosted UI

<Steps>
  <Step title="Initiate Passkey Session">
    Request a passkey session from your backend and receive a hosted UI URL. For creating new accounts, use the base `/passkeys` endpoint. For authentication with existing passkeys, use `/passkeys/auth`.

    <Note>
      <b>appName</b> determines how the passkey will be named when it is created on your user's device. Use your application's name or a clear identifier so users can easily recognize it.
    </Note>

    ```tsx theme={null}
    const requestPasskeySession = async (action: "create" | "auth") => {
      const appName = "YourAppName";

      // Generate a session key
      const keypair = Keypair.generate();
      const sessionKey = {
        key: keypair.publicKey.toBase58(),
        expiration: 900, // 15 minutes
      };

      const redirectUrl = encodeURIComponent(window.location.origin + "/");

      // Use different endpoints for create vs auth
      const endpoint = action === "create"
        ? "https://grid.squads.xyz/api/grid/v1/passkeys"
        : "https://grid.squads.xyz/api/grid/v1/passkeys/auth";

      const response = await fetch(endpoint, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "x-grid-environment": "sandbox", // or 'production'
          "Authorization": "Bearer YOUR_API_KEY",
        },
        body: JSON.stringify({
          meta_info: {
            appName: appName,
            redirectUrl: redirectUrl,
          },
          sessionKey,
        }),
      });

      const data = await response.json();
      setPasskeyUrl(data.url);
      setShowIframe(true);
    };
    ```
  </Step>

  <Step title="Embed Hosted UI">
    Add the hosted Passkey UI to your app using an <code>iframe</code> with the correct WebAuthn permissions.

    ```tsx theme={null}
    <iframe
      allow="publickey-credentials-get *; publickey-credentials-create *"
      src={passkeyUrl}
      style={{ display: "none" }}
      onError={(e) => {
        console.error("Iframe error:", e);
      }}
      onLoad={() => {
        console.log("Iframe loaded successfully");
      }}
    />
    ```
  </Step>

  <Step title="Handle Authentication Result">
    Listen for messages from the iframe to receive the session token or handle errors, then use the session token as a signer in Grid account operations.

    <Note>
      On successful authentication, the iframe will post a message of type <code>authz\_complete</code> containing a <code>sessionToken</code> and the passkey address. For account creation, you'll also receive <code>smartAccountAddress</code>. On error, a message of type <code>authz\_error</code> will be sent with an error description.
    </Note>

    ```tsx theme={null}
    useEffect(() => {
      const handleMessage = (event: MessageEvent) => {
        if (event.data.type === "authz_complete") {
          // event.data.sessionToken: Use this as a signer for Grid operations
          // event.data.passkeyAddress: The onchain address of the passkey
          // event.data.smartAccountAddress: The Grid smart account (for create action)
          console.log("Authorization complete:", event.data);

          const { sessionToken, passkeyAddress, smartAccountAddress } = event.data;

          // For new accounts, store the smart account address
          if (smartAccountAddress) {
            console.log("New smart account created:", smartAccountAddress);
          }

          // Handle successful authentication
        } else if (event.data.type === "authz_error") {
          // event.data.error: Error description
          console.log("Authorization error:", event.data.error);
          // Handle authentication error
        }
      };

      window.addEventListener("message", handleMessage);
      return () => window.removeEventListener("message", handleMessage);
    }, []);
    ```
  </Step>
</Steps>

### React Native Integration - Hosted UI

<Steps>
  <Step title="Initiate Passkey Session">
    Request a passkey session from your backend and receive the hosted UI URL.

    ```javascript theme={null}
    import { Linking } from 'react-native';

    const appName = "YourAppName";
    const action = "create"; // or "auth"
    const apiKey = "YOUR_API_KEY";
    const environment = "sandbox"; // or 'production'
    const redirectUri = Linking.createURL("login");

    // Generate a session key
    const keypair = Keypair.generate();
    const sessionKey = {
      key: keypair.publicKey.toBase58(),
      expiration: 900, // 15 minutes
    };

    // Use different endpoints for create vs auth
    const endpoint = action === "create"
      ? "https://grid.squads.xyz/api/grid/v1/passkeys"
      : "https://grid.squads.xyz/api/grid/v1/passkeys/auth";

    const response = await fetch(endpoint, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${apiKey}`,
        "x-grid-environment": environment,
      },
      body: JSON.stringify({
        meta_info: {
          appName,
          redirectUrl: redirectUri,
        },
        sessionKey,
      }),
    });

    const data = await response.json();
    ```
  </Step>

  <Step title="Open Hosted UI in Browser">
    Launch the Passkey UI in the device browser using <code>WebBrowser.openAuthSessionAsync</code>.

    ```javascript theme={null}
    const result = await WebBrowser.openAuthSessionAsync(data.url, redirectUri);
    ```
  </Step>

  <Step title="Process Redirect Result">
    Handle the redirect and extract the session token from the result for use as a signer in Grid operations.

    ```javascript theme={null}
    // result is encoded in url
    const parsed = LinkingExpo.parse(result.url);
    const status = parsed.queryParams?.status;

    if (status === "success") {
      const passkeyAddress = parsed.queryParams?.passkeyAddress;
      const smartAccountAddress = parsed.queryParams?.smartAccountAddress;

      // Store account addresses for future transactions
      if (smartAccountAddress) {
        console.log("New Grid account created:", smartAccountAddress);
      }
    } else {
      // handle error
      console.error("Passkey error:", parsed.queryParams?.error);
    }
    ```
  </Step>
</Steps>

## Smart Account Creation

When using the `create` action, Grid automatically:

1. **Creates Passkey**: Initiates WebAuthn ceremony on user's device
2. **Deploys Smart Account**: Creates a new 1/1 threshold smart account on Solana
3. **Sets Permissions**: Configures passkey with full permissions (mask=7)
4. **Stores in Database**: Registers account in Grid's database
5. **Funds Account** (Sandbox only): Adds 1 USDC to new sandbox accounts

The response includes both the passkey address and the smart account address that you can use immediately.

## WebAuthn Configuration Requirements

### Required Settings

The on-chain passkey program has specific requirements that must be met for successful registration and authentication:

**User Presence Required**:

* `userPresent` must be `true` in the WebAuthn authenticator response
* This applies to both passkey creation and subsequent authentications
* The authenticator must verify user presence through biometric, PIN, or physical interaction

**Algorithm Requirement**:

* Only ES256 (algorithm `-7`) is supported
* No other cryptographic algorithms are accepted by the on-chain program

**WebAuthn Configuration Example**:

```javascript theme={null}
// For passkey creation
const createOptions = {
  publicKey: {
    challenge: challenge, // From API URL
    rp: { name: "Your App" },
    user: {
      /* user info */
    },
    pubKeyCredParams: [{ alg: -7, type: "public-key" }], // REQUIRED: Only -7 (ES256) supported
    authenticatorSelection: {
      userVerification: "preferred", // Ensures user presence
      requireResidentKey: true,
    },
    attestation: "direct",
  },
};

// For authentication
const getOptions = {
  publicKey: {
    challenge: challenge, // From API URL
    userVerification: "preferred", // Ensures user presence
    timeout: 60000,
  },
};
```

## Handling Registration Failures

**If local passkey creation succeeds but submission fails:**

* The passkey exists locally but has no associated on-chain account
* **Solution**: Re-request a new URL with the create endpoint and submit again
* The existing local passkey will work with the new challenge
* No need to recreate the local passkey

**Common failure scenarios:**

* Challenge expired (60-second timeout)
* Network issues during submission
* Invalid WebAuthn response format

## Timing Constraints

Understanding the timing constraints helps avoid confusing errors:

| Operation                 | Time Limit   | Reason                                           |
| ------------------------- | ------------ | ------------------------------------------------ |
| API challenge (slot hash) | \~3 minutes  | Solana's SlotHashes sysvar retains 512 entries   |
| WebAuthn ceremony         | No limit     | Browser-only operation, not blockchain-dependent |
| Session key               | User-defined | Set via `expiration` parameter (in seconds)      |

<Note>
  The passkey *credential* on the user's device has no expiration. The timing constraint applies to the *challenge* (slot hash) used in API calls, not the credential itself. A passkey created months ago can sign fresh challenges.
</Note>

**Why this matters:** If a user takes too long between requesting a passkey session and completing the flow, the slot hash becomes invalid. The solution is simple—request a new session URL with a fresh challenge.

## Best Practices

1. **Challenge Handling**:

   * Parse challenge from URL parameters
   * Use challenge directly - it's already base64 encoded
   * Never re-encode the challenge
   * Return complete WebAuthn response objects
   * Complete WebAuthn ceremony within 60 seconds of URL generation

2. **Session Management**:

   * Generate unique session keys for each authentication
   * Implement proper expiration handling
   * Securely store session tokens
   * Never expose session keys

3. **Error Handling**:

   * Implement comprehensive error handling
   * Provide user-friendly error messages
   * Log errors for debugging
   * Handle both success and failure scenarios

4. **User Experience**:

   * Use clear, recognizable app names
   * Provide loading states during authentication
   * Show clear feedback for success/failure
   * Explain passkey benefits to users

5. **Security**:

   * Validate all inputs
   * Use HTTPS for all communications
   * Implement proper CORS policies
   * Never expose API keys in client-side code
   * Store account addresses securely
   * Never confuse sandbox and production addresses

6. **Environment Management**:
   * Maintain separate accounts for sandbox and production
   * Never send real funds to sandbox addresses
   * Test thoroughly in sandbox before production deployment

For more details, see the API endpoint documentation:

* [Create Passkey Session](/grid/v1/api-reference/endpoint/passkeys/post)
* [Authorize Passkey Session](/grid/v1/api-reference/endpoint/passkeys/auth)
* [Submit Passkey Session](/grid/v1/api-reference/endpoint/passkeys/submit)
* [Create Smart Account](/grid/v1/api-reference/endpoint/passkeys/create-account)

## Advanced Integration

**Need Your Own Custom Domain?** If you need to host passkey flows on your own domain (e.g., `auth.yourcompany.com`) for branding or compliance requirements, see [Advanced Integration](/grid/v1/accounts/passkeys/advanced-integration).
