> ## 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.

# Building your auth flow

> Collect and store user credentials in your application.

Build your own credential collection flow with full control over the look, feel, and UX, and Deck stores the credentials securely in the [Credential Vault](/platform/credential-management).

<Tip>
  For simple credential collection flows that don't require customization, the pre-built [Auth Component](/components/auth) is available as a drop-in React component.
</Tip>

## How it works

```mermaid theme={null}
sequenceDiagram
    participant User
    participant Server as Your Server
    participant Deck as Deck API

    User->>Server: Submit credentials (HTTPS)
    Server->>Deck: POST /v2/credentials
    Deck-->>Server: credential (status: unverified)
    Server-->>User: Show success
```

Credentials go directly from the user to your server over HTTPS. Your server stores them with Deck. The credential is created immediately with `unverified` status and becomes `verified` the first time a task run authenticates successfully.

## Credential statuses

When you first store a credential, it starts as `unverified`. The credential is verified automatically when it is used successfully in a [task run](/concepts/task-runs). During that task run, the source may require additional input from the user — like an MFA code or a security question — which puts the task run into an `interaction_required` state. Once any required interactions are resolved and the task run completes successfully, the credential moves to `verified`. If the source rejects the credentials, the status becomes `invalid`.

| Status       | What to show                                                |
| ------------ | ----------------------------------------------------------- |
| `unverified` | Credentials stored. Ready to use on task runs.              |
| `verified`   | Credentials confirmed working.                              |
| `invalid`    | Source rejected the credentials. Prompt the user to update. |
| `deleted`    | Credential was permanently removed.                         |

## Step-by-step implementation

<Steps>
  <Step title="Build a credential form">
    Start with a form that collects the credentials required by the source. Most sources use `username_password`.

    ```tsx theme={null}
    function StoreCredentials({ sourceId }: { sourceId: string }) {
      const [status, setStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle')

      async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
        e.preventDefault()
        setStatus('saving')

        const form = new FormData(e.currentTarget)
        const res = await fetch('/api/credentials', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            source_id: sourceId,
            email: form.get('email'),
            password: form.get('password'),
          }),
        })

        if (res.ok) {
          setStatus('saved')
        } else {
          setStatus('error')
        }
      }

      if (status === 'saving') return <p>Saving credentials...</p>
      if (status === 'saved') return <CredentialSaved />
      if (status === 'error') return <p>Something went wrong. Please try again.</p>

      return (
        <form onSubmit={handleSubmit}>
          <label htmlFor="email">Email</label>
          <input id="email" name="email" type="email" required />

          <label htmlFor="password">Password</label>
          <input id="password" name="password" type="password" required />

          <button type="submit">Save Credentials</button>
        </form>
      )
    }
    ```
  </Step>

  <Step title="Store the credential server-side">
    Your server receives the credentials and stores them with Deck. This can be an Express handler, a Next.js API route, or any server-side endpoint.

    ```typescript theme={null}
    app.post('/api/credentials', async (req, res) => {
      const { source_id, email, password } = req.body

      const credential = await fetch('https://api.deck.co/v2/credentials', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.DECK_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          source_id,
          auth_method: 'username_password',
          auth_credentials: { username: email, password },
          external_id: req.user.id, // your internal user ID, used to map the credential back to the user in your system
        }),
      })

      const data = await credential.json()
      res.json(data)
    })
    ```

    The response comes back with `status: "unverified"`. The credentials are encrypted and stored in the Credential Vault.
  </Step>

  <Step title="Listen for credential events">
    Deck sends [events](/events/events) to your [event destination](/events/destinations-and-deliveries) when credential statuses change.

    ```typescript theme={null}
    app.post('/webhooks/deck', async (req, res) => {
      const event = req.body

      switch (event.type) {
        case 'credential.verified':
          await notifyClient(event.data.external_id, {
            status: 'verified',
            credential_id: event.data.id,
          })
          break

        case 'credential.invalid':
          await notifyClient(event.data.external_id, {
            status: 'invalid',
            message: 'Credentials were rejected. Please update them.',
          })
          break

        case 'task_run.interaction_required':
          // The source is asking for user input (e.g. MFA code, security question)
          await notifyClient(event.data.credential_id, {
            status: 'interaction_required',
            task_run_id: event.data.id,
            interaction: event.data.interaction, // contains type, message, and fields
          })
          break
      }

      res.sendStatus(200)
    })
    ```

    ### Handling interactions

    Some sources require additional input during a task run, like an MFA code or a security question. When this happens, the task run pauses and Deck fires a `task_run.interaction_required` event. The event includes an `interaction` object that describes what the source needs:

    ```json theme={null}
    {
      "type": "mfa",
      "message": "Enter the verification code sent to your phone",
      "fields": [
        {
          "name": "code",
          "type": "string",
          "label": "Verification code"
        }
      ]
    }
    ```

    Use the `fields` array to render a form that collects the required input. Once you have the user's response, submit it to resume the task run:

    ```typescript theme={null}
    app.post('/api/task-runs/:taskRunId/interaction', async (req, res) => {
      const response = await fetch(
        `https://api.deck.co/v2/task-runs/${req.params.taskRunId}/interaction`,
        {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.DECK_API_KEY}`,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            input: { code: req.body.code },
          }),
        }
      )

      const data = await response.json()
      res.json(data)
    })
    ```

    The task run resumes where it left off and continues to completion. A task run may require multiple interactions — for example, an MFA code followed by a security question. See the [Interactions guide](/guides/interactions) for more detail.
  </Step>

  <Step title="Show success">
    Once the credential is stored, show a success state and move the user forward in your product.

    ```tsx theme={null}
    function CredentialSaved() {
      return (
        <div>
          <h3>Credentials saved</h3>
          <p>Your account has been linked. Credentials will be verified on the first task run.</p>
        </div>
      )
    }
    ```
  </Step>
</Steps>

## Handling errors

Build your UI to handle common failure cases gracefully:

| Scenario            | What happened                                           | Recommended UX                                                                                     |
| ------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| Invalid credentials | Source rejected the username/password during a task run | Show an error. Let the user [update the credential](#updating-credentials) with corrected details. |
| Source unavailable  | The target website is down or blocking                  | Show a temporary error. Suggest trying again later.                                                |
| Credential deleted  | Credential was permanently removed                      | Prompt the user to add new credentials.                                                            |

## Updating credentials

When a credential becomes `invalid`, prompt the user to re-enter their details and update the credential with a `PATCH` request.

```typescript theme={null}
app.patch('/api/credentials/:credentialId', async (req, res) => {
  const { email, password } = req.body

  const credential = await fetch(
    `https://api.deck.co/v2/credentials/${req.params.credentialId}`,
    {
      method: 'PATCH',
      headers: {
        'Authorization': `Bearer ${process.env.DECK_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        auth_credentials: { username: email, password },
      }),
    }
  )

  const data = await credential.json()
  res.json(data)
})
```

The credential status resets to `unverified` and will be re-verified on the next task run.
