Skip to content

SPA Components

The bundled AuthWorkflow forms are server-driven atscript types — the server decides which form to render at each pause and ships it as an annotated schema. Your SPA renders them with <AsWfForm> from @atscript/vue-wf and registers a small set of companion Vue components by name. This page covers the aooth-side contract: which component-name strings the bundled forms emit, and how to wire them. The component packages themselves (@atscript/vue-wf, @atscript/vue-aooth, @atscript/vue-form) are documented in the atscript-ui docs.

The contract: @ui.form.component '<Name>'

Three bundled form fields carry a @ui.form.component '<Name>' annotation naming a Vue component. <AsForm> (inside <AsWfForm>) resolves that string against the :components map you pass — so the string in the .as schema MUST match a key in your :components map, or the field falls back to the default renderer.

Form field@ui.form.componentComponent (@atscript/vue-aooth)Renders
WithInlineConsentForm.consentsAsConsentArrayAsConsentArrayone checkbox per pending consent; self-hides when none pending
SetPasswordForm.passwordRulesAsPasswordRulesAsPasswordRuleslive password-policy fulfillment dots, re-evaluated per keystroke
EnrollConfirmForm.qrCodeAsQrCodeAsQrCodescannable TOTP otpauth:// QR + the base32 secret for manual entry
LoginCredentialsForm.ssoProviderAsSsoProvidersAsSsoProvidersone-click SSO provider buttons; self-hides when none configured

All four components come from @atscript/vue-aooth (an external SPA package), not from any @aooth/* package.

Minimum wiring

vue
<script setup lang="ts">
import { AsWfForm, AsWfFinish, type WfFinished } from "@atscript/vue-wf";
import { createDefaultTypes } from "@atscript/vue-form";
import { AsConsentArray, AsPasswordRules, AsQrCode, AsSsoProviders } from "@atscript/vue-aooth";

const types = createDefaultTypes();
// Keys here MUST match the `@ui.form.component '<Name>'` strings in the bundled forms.
const components = { AsConsentArray, AsPasswordRules, AsQrCode, AsSsoProviders };
</script>

<template>
  <AsWfForm
    path="/auth/trigger"
    name="auth/login/flow"
    :types="types"
    :components="components"
    :navigate="navigate"
    @finished="onFinished"
    @error="onError"
  />
</template>
  • path — the /auth/trigger endpoint (the AuthController route the workflow runs behind).
  • name — the workflow id to start: auth/login/flow, auth/invite/start, auth/recovery/flow, or auth/signup/flow.
  • :types — the built-in field renderers from @atscript/vue-form.
  • :components — the aooth companion components, keyed by the names the forms emit.

AsQrCode — TOTP enrollment

During MFA enrollment, the EnrollConfirmForm.qrCode field carries the otpauth:// provisioning URI (built from the secret + your totpIssuer opt) and @ui.form.component 'AsQrCode'. AsQrCode renders it as a scannable SVG and extracts the base32 secret from the URI to show for manual entry (its manualSecret prop defaults on) — so there is no separate "manual secret" field. The user scans/enters it in their authenticator, then submits the 6-digit code, which the server verifies via UserService.verifyTotpSetupCode.

AsQrCode has an optional peer dependency on qrcode for the SVG render — install it in the SPA if you want the visual QR (without it, the manual secret still renders).

AsConsentArray — pending consents

When ConsentStore.getPendingConsents(subject) returns descriptors (the arg is the stable user id), the workflow transports them (via @wf.context.pass) to whichever form the user is currently on, and the inline consents: string[] field renders one checkbox per descriptor through AsConsentArray. The field self-hides when nothing is pending — no @ui.form.fn.hidden needed. Submitted ids are validated server-side against the pending set (the authoritative whitelist). See Workflows — consent collection.

AsPasswordRules — live policy readout

On SetPasswordForm, the phantom passwordRules field renders fulfillment dots that re-evaluate on every keystroke against data.newPassword, using the same transferable policy expressions the server enforces (shipped to the client via UserService.getTransferablePolicies()). See Password Policies.

AsSsoProviders — federated login buttons

When you offer federated providers (resolveAlternateCredentials().ssoProviders), the login form's LoginCredentialsForm.ssoProvider field renders them through AsSsoProviders: a one-click picker that paints each provider as a button labelled with its server-owned text ("Continue with Google"), applies the provider's icon class verbatim, and on click both selects the id and fires the field's data-carrying sso action — there is no separate submit button, and the field self-hides when no providers are configured. The selected provider rides the partial submit and AuthWorkflow.beginSso turns it into the provider 302. See Federated Login.

The icon is a CSS class you own: because the icon strings arrive from server context, a UnoCSS-style static extractor never sees them in source — safelist each one and install the matching icon collection (the demo wires i-simple-icons:google via a second presetIcons + a safelist entry). The component-API details (props, secondary chips, the "or" divider) live in the @atscript/vue-aooth docs.

Cross-flow alt-action links

The bundled forms' cross-flow navigation actions — login's signup ("Don't have an account? Sign up") and magicLink, the signup form's backToLogin ("Already have an account? Sign in"), and recovery's backToLogin ("Remembered your password? Sign in") — render as pushed-down, centered "text + link" affordances below the submit button, rather than inline above it. Three @atscript/vue-aooth / @atscript/vue-form annotations on the ui.action field drive this:

AnnotationEffect
@ui.form.pushDownMoves the action into its own grid below the submit button (def.pushDownFields) instead of inline above it.
@ui.form.attr 'text', '…'Renders leading text before the link → text [link] (e.g. "Don't have an account? Sign up").
@ui.form.attr 'align', 'left' | 'center' | 'right'Aligns the text+link row. The bundled cross-flow links use center.

@atscript/moost-wf serializes all three over the workflow round-trip, so a server-driven <AsWfForm> renders them identically. The text/align attrs apply only to the standalone ui.action button (the AsAction component) — not to the inline-on-input action variant (e.g. login's forgotPassword, which sits in the password field's footer). The annotation semantics are owned by the atscript-ui docs; the bundled forms only declare them. To restyle or relabel, replace the form via opts.forms.<slot> and keep/adjust the annotations (action ids stay the routing contract — see Workflows).

auth/recovery/flow and auth/invite/start finish their first leg by emailing a URL carrying a wfs=<token> (and, for invites, a uid=<userId>). Route that into your workflow page and pass it as :initial-token so <AsWfForm> resumes the paused state instead of starting fresh:

vue
<AsWfForm path="/auth/trigger" :name="wfId" :initial-token="initialToken" ... />

For a re-clicked invite link whose state row has already been evicted, fall through to GET /auth/invite/post-redemption?uid=<userId> and hand the returned WfFinished to <AsWfFinish> for envelope-shape parity. See REST Controllers — post-redemption.

Custom config per request — variant headers

To pick a server-side workflow preset per request, send a custom header via :fetch-options and re-key the form so it remounts when the choice changes:

ts
const fetchOptions = computed(() =>
  variant.value ? { headers: { "x-wf-variant": variant.value } } : undefined,
);

The server reads the header in its AuthWorkflow subclass (e.g. in an @Injectable("FOR_EVENT") constructor) to merge a variant config. Reference: packages/e2e-demo/src/ui/pages/WfPage.vue.

Replacing a bundled form keeps the component string

If you swap a bundled form via opts.forms.<slot> (typically extends the bundled one), keep the @ui.form.component '<Name>' annotation on the field if you want the companion widget — the :components registration is keyed on the string, not the form class.

See also

  • Workflows — the server side that emits these forms.
  • Atscript Models — the bundled form catalogue + their annotations.
  • atscript-ui docs<AsWfForm>, <AsWfFinish>, and the @atscript/vue-aooth component APIs.

Released under the MIT License.