How it works
- Your server creates a session token via
POST /v2/token - Your frontend mounts
<DeckAuthComponent />with the token and all configuration - The component renders a secure UI (hosted at
auth.components.deck.co) where the user selects a source and enters credentials - Credentials go from the component directly to the Deck API and into the Credential Vault. They never touch your page
- On success, the component returns the new Credential ID via
onSuccess
Restrictions
- Collects credentials via
username_password(default) orsource_fields(opt in per source throughsourceConfig). No other auth method is supported.
Installation
Quick example
Create a session token on your server:Client
Session token
Every auth component session starts with a server-side token. The token is authentication only. It proves the request came from your backend. All configuration (sourceId, appearance, etc.) is passed on the component, not the token.POST /v2/token
Creates a session token for the Auth Component.
Authentication: Authorization: Bearer sk_live_...
Request body: None
Response (201):
Token lifecycle
- Tokens expire 30 minutes after creation
- Expired tokens return a clear error to
onError
Component props
The component does not own its lifecycle. Your app does. When
onClose fires, you decide what happens (unmount, navigate, show a confirmation, etc.).
Callbacks
onSuccess
onCancel
onClose.
onError
type is the error category. code is the machine-readable identifier. message is human-readable and may change. Switch on type and code, not message. See Error handling for all error codes.
onClose
onClose fires when the user dismisses the flow — via the persistent close (X) icon, the Escape key, or a Cancel/Close button on the canceled or error screen. You handle the teardown.
By default the X is shown whenever onClose is provided and hidden when it is omitted. To control the two independently — for example, to embed the component in your own modal that already has a close control — pass showCloseButton:
onClose entirely hides the X and leaves the Cancel/Close buttons with no handler, so prefer showCloseButton={false} when you still want those buttons to work.
If a task run is in progress when the user closes the component, the task run continues running in the background. To cancel it, use the cancel endpoint from your onClose handler.
showCloseButton
onClose. Defaults to onClose != null, so existing integrations are unaffected. Pass onClose together with showCloseButton={false} to hide the X while keeping the Cancel/Close buttons (which share the same close handler) functional.
Source selection
ThesourceId prop controls what the user sees before credential capture.
Source fields
Some sources need values beyond username/password: an account number, a company ID, etc. ThesourceConfig prop attaches source fields to a source’s credential form, keyed by source ID (or "*" as a default for any source without its own entry). By default these fields are collected alongside username/password, but a source can also authenticate on source fields alone (see Auth method). Collected values are stored on the credential under auth_credentials.source_fields; fields marked tokenized are vaulted.
Only sources that need custom fields get an entry. Every other source renders the standard username/password form.
Entry config
Each entry insourceConfig is keyed by source ID (or "*") and takes:
Auth method
authMethod controls whether the entry’s fields sit alongside username/password or replace them.
A
source_fields entry must declare at least one field; one with none fails fast at mount with source_fields_auth_requires_fields, and an unknown authMethod fails with auth_method_invalid. If the source doesn’t support the selected method, the flow fails at runtime with auth_method_not_supported.
Field config
Every display string (label, placeholder, and each option label) is a LocalizedString: either a plain string, rendered as-is in every locale, or a { "en-US": string; "fr-CA"?: string } map picked by language, falling back to "en-US". The two forms can be mixed within one config.
Field validation
Astring field can declare format constraints via validation. The component checks them in the form (on blur, then live as the user edits) and keeps the submit button disabled while a value violates them. The check is client-side, so it narrows what the user can submit but does not replace the source’s own validation.
The keys combine:
{ pattern: '\\d+', max: 9 } is a digits-only field capped at 9.
language. A pattern violation shows your message instead, since only you know the format the source expects, so the component supplies no default. With no message, a pattern violation still marks the field invalid and blocks submit, it just renders no text.
- Empty values are never format-checked.
requiredcovers those, and an optional field left blank is omitted from the submission. - Prefills (
value, or a value restored on reconnect) are checked at mount, so an invalid prefill explains the blocked submit without waiting for a blur. - Malformed constraints fail fast at mount with
source_field_config_invalid: apatternthat isn’t a valid regex, a negative or non-integer length bound,minLengthabovemaxLength,minabovemax, orvalidationon a type other than"string".
Behavior
- Scoping is per source: the entry matching the selected source applies, and
"*"covers any source without its own entry. - Stored values are always strings: selects store the option
value, booleans store"true"/"false". Option values and stored values are data and are never localized. - Reconnect (Update Mode) prefills non-tokenized fields from the stored credential and keeps them editable. Tokenized fields render empty (their values are never returned) and are treated as optional even when
required: leave one blank to keep the stored value, or re-enter it to change it, so users aren’t forced to re-enter vaulted values.
Invalid
sourceConfig fails fast: the component fires onError at mount with a source_field_* code rather than erroring at credential creation. See Error handling.Update mode
Pass acredentialId to re-collect credentials for an existing credential instead of creating a new one.
Task linking
Pass ataskId to verify credentials immediately after creation. The component fires a task run using the new credential, and the task run view stays visible while it executes.
If the task declares fields in its input_schema, the component collects them from the user (or from you, silently) before firing the run. See Task input.
Credential status reflects whether the agent could authenticate, not whether the run finished successfully. As long as auth succeeds, the credential becomes
verified even if the run later fails. A failed run does not delete or invalidate the credential. If auth itself fails, the credential becomes invalid.
taskRunView
With taskId present, the task run view is shown while the run executes:
"status": spinner with status text (e.g., “Connecting…”, “Verifying credentials…”)"live": live view of the task run
taskId, no task run view is shown.
Task input
Some tasks declare input in theirinput_schema: a date range, an account number, a file to upload. The component supports chaining to these tasks in both auth mode and interaction mode.
Whenever a taskId is provided, the component fetches the task’s input_schema at mount and presents its fields to the end user in a task details step: after credential submit in auth mode, before the run in interaction mode. The heading of the step is a generic “Additional details” (the end user doesn’t know a task is involved). The step is skipped only when the schema declares no fields, or every field is supplied silently.
The optional taskInput prop refines that presentation. The schema stays the source of truth for structure: field types, required fields, enum values, and tokenization all come from the schema. taskInput supplies what the schema can’t: values you already know (passed silently, nothing rendered), localized labels and placeholders, prefills, and the step heading.
taskInput takes:
Each
fields entry is either a raw scalar (a silent pass-through: merged into the run’s input, never shown) or a config object (presented to the user unless hidden), the same pattern as sourceConfig. Schema fields with no entry are presented with the schema property name as their label. taskInput requires a taskId; without one the component fires onError with task_input_requires_task at mount.
Task input field config
Display strings (
title, label, placeholder, option labels) are the same LocalizedString type as source fields: a plain string, or a { "en-US": string; "fr-CA"?: string } map picked by language.
Unlike sourceConfig, there is no type, required, or tokenized key: those come from the schema. validation is yours to declare, because the schema states a field’s type and required-ness but not its format:
pattern and the lengths check the typed text, min/max check it as a number. A malformed block fails fast at mount with task_input_config_invalid; set on a schema kind other than string or number, validation fires task_input_invalid at mount rather than being silently ignored. A value passed silently with hidden: true never reaches the form, so its constraints are checked once at mount instead.
Field rendering
The schema property’s type drives the input control:
Fields the schema marks
required must be filled before the user can continue, and any field carrying a validation block must satisfy it; presented optional fields left empty are omitted from the run’s input. Fields in the schema’s tokenized array render masked and are handled like credentials: never logged and never put in URL parameters.
Bad task input configuration fails fast at mount, before a credential is created. See Error handling.
Interaction mode
Setmode="interaction" to collect interaction inputs (MFA code, security question, MFA method) from the user during a task run against an existing credentialId or sourceId. The component skips source selection and credential capture, triggers the task run, and renders any pending interaction in place.
If the task declares fields in its input_schema, the component collects them in a task details step before triggering the run.
Required props
Interaction mode needstaskId plus one of:
Both are validated before the component mounts:
- Omitting
taskIdfiresonErrorwithtask_id_required. - Providing neither a
credentialIdnor a singlesourceIdfiresonErrorwithcredential_or_source_required.
Appearance
Customize the component’s look via theappearance prop.
Theme
Variables
Branding
Upload a logo in the Deck console under Settings → Branding. Deck hosts it and returns a CDN URL on
content.cdn.deck.co. Pass that as logoUrl. Accepted formats are PNG, JPG, JPEG, GIF, and WebP (no SVG). The logo renders into a 40×40 box and is hidden if it fails to load.
For security, the component only loads images from Deck’s content CDN,
data: URIs, and cdn.brandfetch.io. Arbitrary external URLs (for example, https://your-company.com/logo.png) are blocked by its content security policy and will not render.Language
Set the language for all component-rendered copy via thelanguage prop.
language applies to copy the component owns: source picker, credential capture labels and helper text, interaction prompts (generic labels and submit/continue buttons), in-UI error messages, the task run status view (taskRunView: "status"), and the close button and accessibility labels.
Content returned by the source itself (source names, interaction field labels, and other source-defined strings) is not translated by the component and is rendered as the source returns it.
Interactions
Sources may require additional input mid-flow: an MFA code, a security question, an account selection. The component handles these automatically as interactions: a generic pause-and-prompt primitive. When an interaction is required:- The component transitions to an interaction step and renders the fields from the source’s response
- The user fills in the fields and submits
- The source validates. On accept, the flow continues
- Some sources require multiple sequential interactions (e.g., MFA code then a security question)
Error handling
All errors surface throughonError with a uniform { type, code, message } shape. code falls into two groups by where it comes from.
Validation errors
Raised by the component itself before any network request, from checking the token and the prop combination. These are deterministic: a given misconfiguration always produces the same code.Runtime errors
Surfaced by the Deck API and the source as the flow runs. The component forwards the API’stype, code, and message (mapping a couple of generic not-found codes to the specific ones below). Treat this as the set you are most likely to encounter, not an exhaustive list, and handle any unrecognized code as a generic failure.
Error behavior
- Validation errors fire before the iframe mounts. Fix the token or props and remount.
- Task input errors (
task_input_invalid,task_read_failed) fire at mount, after the task schema fetch but before any credential is created. Fix the config (or the task) and remount. - Token expiry (
token_invalid) terminates the session. Create a new token and remount. - Credential and source errors (
auth_invalid,account_locked, etc.) terminate the session. - Interaction errors (
interaction_timeout) terminate the session. Wrong user input is not an error; the source emits another prompt and the user retries in place.
Embed by URL
For clients that can’t run the React SDK, load the component’s URL in aWebView (for example, in a React Native app) or an <iframe> and pass configuration as query parameters:
URL parameters
Appearance parameter
appearance takes the same AppearanceConfig object as the prop, encoded as a URL-safe JSON string:
theme shorthand instead.
Examples
Configuring via postMessage
URL parameters are subject to URL-length limits, so a largesourceConfig or taskInput (or any object config you’d rather not URL-encode) is best delivered over postMessage, which has no encoding step and no length limit. Tokenized task input values must only be sent this way. The React component uses this same handshake internally.
Load the component with no configuration in the URL (just the bare origin), wait for the READY message, then post an INIT message with the full configuration object:
WebView, run the same window.postMessage({ type: 'INIT', ... }) (via injectJavaScript) once you receive READY through onMessage.
Receiving results
Without React callbacks, the component delivers results as messages to the client:- React Native
WebView: messages arrive viawindow.ReactNativeWebView.postMessageas JSON strings. Read them with theWebView’sonMessagehandler. <iframe>: the same messages arrive viawindow.postMessageto the parent frame.
type and ignore any you don’t handle:
Each message is
{ type, payload }: