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

# Build a Custom Authentication UI

> Learn how to build a custom authentication flow using the Deck API

## When to Build a Custom UI

For most use cases, we recommend using the [Auth SDK](/guides/authentication/deck-auth). It’s quick to implement and automatically handles MFA and other edge cases.
Build a custom authentication UI only when you:

* **Need full UI control** - Your design requires a specific authentication experience that doesn't fit the SDK's UI
* **Have existing auth flows** - You want to integrate Deck into an existing authentication system

## Prerequisites

* Your Deck API credentials. [Quickstart Guide](/guides/guides/quickstart)
* A webhook endpoint configured and ready to receive webhooks. [Webhooks Guide](/guides/webhooks/webhooks)
* Source GUIDs for the data sources you want to connect to. [Sources Guide](/guides/guides/sources)

## Integration Guide

<Steps>
  <Step title="Set up environment variables">
    Configure your Deck API credentials as environment variables. These are required to generate SDK tokens and must never be exposed in frontend code. Both keys are available in your [Dashboard](https://dashboard.deck.co).

    **.env**

    ```bash theme={null}
    DECK_CLIENT_ID=your_client_id_here
    DECK_CLIENT_SECRET=your_secret_here
    ```
  </Step>

  <Step title="Build Credential Collection Form">
    Create a form to collect user credentials. Most sources require username and password, but requirements vary by provider.

    ```jsx theme={null}
    function CredentialForm({ onSubmit, isLoading }) {
      const [username, setUsername] = useState('');
      const [password, setPassword] = useState('');

      const handleSubmit = (e) => {
        e.preventDefault();
        onSubmit({ username, password });
      };

      return (
        <form onSubmit={handleSubmit}>
          <div>
            <label>Username or Email</label>
            <input
              type="text"
              value={username}
              onChange={(e) => setUsername(e.target.value)}
              required
            />
          </div>
          <div>
            <label>Password</label>
            <input
              type="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              required
            />
          </div>
          <button type="submit" disabled={isLoading}>
            {isLoading ? 'Connecting...' : 'Connect Account'}
          </button>
        </form>
      );
    }
    ```
  </Step>

  <Step title="Run the EnsureConnection Job">
    When the user submits credentials, call the `EnsureConnection` job from your server-side code. You will need to pass both the user's credentials as well as the source you want to connect to. This job runs asynchronously and returns results via webhook.

    <Info>Connections stay open for 10 minutes without receiving another job.</Info>

    ```bash theme={null}
    curl --request POST \
      --url https://live.deck.co/api/v1/jobs/submit \
      --header 'x-deck-client-id: <your-client-id>' \
      --header 'x-deck-secret: <your-secret>' \
      --header 'Content-Type: application/json' \
      --data '{
        "job_code": "EnsureConnection",
        "input": {
          "source_guid": "<source-guid>",
          "username": "<username>",
          "password": "<password>"
        }
      }'
    ```

    **Available parameters on `EnsureConnection`**

    <ParamField path="source_guid" type="string" required>
      The unique identifier of the source you want to connect to. See the [Sources guide](/guides/guides/sources) for more information.
    </ParamField>

    <ParamField path="username" type="string">
      Your user's username or email for the source they're connecting to.
    </ParamField>

    <ParamField path="password" type="string">
      Your user's password for the source they're connecting to.
    </ParamField>

    <ParamField path="external_id" type="string">
      Customer identifier from your system. Link a Deck connection to your customer ID to look up connections by your external identifier in the [Dashboard](https://dashboard.deck.co).
    </ParamField>

    <ParamField path="access_token" type="string">
      An existing access token from a previous connection. Use this to establish a connection and run jobs in subsequent sessions without the user present.
    </ParamField>

    <ParamField path="webhook_url" type="string">
      Custom webhook URL to receive events for this connection. Overrides the default webhook URL configured in your Dashboard.
    </ParamField>

    <ParamField path="account_id" type="string">
      Used when a set of credentials has multiple accounts attached to it and you want to target a specific account.
    </ParamField>
  </Step>

  <Step title="Handle MFA Challenges (If Required)">
    If the source or user's preferences requires MFA, your webhook will receive an MFA challenge. Display the appropriate UI to collect the MFA answer and submit it.

    **Collect MFA Answer from User:**

    ```jsx theme={null}
    function MfaForm({ message, onSubmit, isLoading }) {
      const [answer, setAnswer] = useState('');

      return (
        <form onSubmit={(e) => {
          e.preventDefault();
          onSubmit(answer);
        }}>
          <label>{message}</label>
          <input
            type="text"
            value={answer}
            onChange={(e) => setAnswer(e.target.value)}
            placeholder="Enter code"
            required
          />
          <button type="submit" disabled={isLoading}>
            Verify
          </button>
        </form>
      );
    }
    ```

    **Submit MFA Answer:**

    ```bash theme={null}
    curl --location 'https://live.deck.co/api/v1/jobs/mfa/answer' \
      --header 'x-deck-client-id: <api-key>' \
      --header 'x-deck-secret: <api-key>' \
      --header 'Content-Type: application/json' \
      --data '{
        "job_guid": "b3ab44de-f722-4685-ab3a-b27b369e859b",
        "answer": "123456"
      }'
    ```
  </Step>

  <Step title="Receive Access Token via Webhook">
    Once authentication is successful, your webhook receives the access token.

    **Success Webhook Payload:**

    ```json highlight={4} theme={null}
    {
      "job_guid": "b3ab44de-f722-4685-ab3a-b27b369e859b",
      "output": {
        "access_token": "access-production-xyz789..."
      },
      "webhook_type": "Job",
      "webhook_code": "EnsureConnection",
      "environment": "Production"
    }
    ```
  </Step>

  <Step title="Use Access Tokens to Run Jobs">
    Use the access token to run jobs on behalf of the user. Jobs can read data (e.g., fetch documents, invoices) or write data (e.g., submit orders, update records).

    Job results are also delivered via webhook. See the [Jobs guide](/guides/guides/jobs) for more information.
  </Step>
</Steps>
