Authentication

Sign users in with email + password, magic codes, passkeys or social providers. <u-signin-root> drives a multi-step flow: you declare one <u-signin-step> per state, the SDK decides which one is active — and <u-conditional-render> adapts each step to what the account actually supports.

View this guide as Markdown

Complete sign-in flow

One u-signin-root covering the whole journey: email → verification (password, magic code, or reset), plus the magic-code, reset-password and missing-fields steps. Conditions like auth.passwordEnabled and auth.magicCodeEnabled make the same markup work for every account type — passwordless accounts are offered a password reset instead of a dead end.

  • Multi-step state machine — one <u-signin-step> per state, SDK-controlled
  • Adaptive verification — password / magic code / reset offered per account
  • Missing fields — collects newly-required profile fields at sign-in
  • Auto-hide when signed in — the form disappears once authenticated
<!-- The root manages the flow; each u-signin-step shows when its step is active.
     The whole form hides automatically once the user is signed in. -->
<u-signin-root id="sign-in-root" class="flex flex-col gap-2">
  <!-- Step 1: email -->
  <u-signin-step name="email">
    <u-email-field
      placeholder="Enter your email"
      class-name="px-4 py-2 mb-2 border border-border rounded-lg w-full"></u-email-field>
    <u-error-message for="email" class-name="mb-2 text-sm text-danger"></u-error-message>
    <u-submit-button
      for="email"
      text="Continue with email"
      class-name="px-4 py-2 text-white bg-accent hover:bg-accent-hover rounded-lg disabled:opacity-50 w-full cursor-pointer"
    ></u-submit-button>
    <u-error-message for="general" class-name="mt-2 text-sm text-danger"></u-error-message>
  </u-signin-step>

  <!-- Step 2: verification — offers exactly what the account supports -->
  <u-signin-step name="verification">
    <u-back-button class-name="mb-3 text-sm text-primary cursor-pointer">← Back</u-back-button>

    <u-conditional-render when="auth.passwordEnabled">
      <u-password-field
        placeholder="Enter your password"
        class-name="px-4 py-2 mb-1 border border-border rounded-lg w-full"></u-password-field>
      <u-reset-password-button class-name="mb-3 text-sm text-primary cursor-pointer"
      ></u-reset-password-button>
      <u-error-message for="password" class-name="mb-2 text-sm text-danger"></u-error-message>
      <u-error-message for="resetPassword" class-name="mb-2 text-sm text-danger"></u-error-message>
      <u-submit-button
        for="password"
        class-name="px-4 py-2 text-white bg-accent hover:bg-accent-hover rounded-lg w-full cursor-pointer"
      ></u-submit-button>
    </u-conditional-render>

    <u-conditional-render when="auth.magicCodeEnabled">
      <u-conditional-render when="auth.magicCodeSent" is="false">
        <u-send-magic-code-button
          text="Send me a magic code instead"
          class-name="mt-3 px-4 py-2 text-primary bg-primary-soft rounded-lg text-center w-full cursor-pointer disabled:opacity-50"
        ></u-send-magic-code-button>
      </u-conditional-render>
    </u-conditional-render>

    <!-- Passwordless account? Offer to set one. -->
    <u-conditional-render when="auth.passwordEnabled" not>
      <u-reset-password-button class-name="mt-3 text-sm text-primary cursor-pointer">
        Set a password for this account
      </u-reset-password-button>
    </u-conditional-render>
  </u-signin-step>

  <!-- Step 3 (magic code path) -->
  <u-signin-step name="magic-code">
    <p class="mb-3 text-center text-sm text-text-light">Enter the code we sent to your email</p>
    <u-magic-code-field class-name="px-4 py-2 mb-2 rounded-lg"></u-magic-code-field>
    <u-error-message for="magicCode" class-name="mb-2 text-center text-sm text-danger"
    ></u-error-message>
    <u-send-magic-code-button
      text="Resend magic code"
      class-name="px-4 py-2 text-primary bg-primary-soft rounded-lg text-center w-full cursor-pointer disabled:opacity-50"
    ></u-send-magic-code-button>
  </u-signin-step>

  <!-- Reset-password path (opened via u-reset-password-button email link) -->
  <u-signin-step name="reset-password">
    <p class="mb-3 text-center text-sm text-text-light">Choose a new password</p>
    <u-password-field
      for="new-password"
      placeholder="New password"
      class-name="px-4 py-2 mb-2 border border-border rounded-lg w-full"></u-password-field>
    <u-password-field
      for="password-confirmation"
      placeholder="Confirm password"
      class-name="px-4 py-2 mb-2 border border-border rounded-lg w-full"></u-password-field>
    <u-error-message for="resetPassword" class-name="mb-2 text-sm text-danger"></u-error-message>
    <u-submit-button
      for="resetPassword"
      class-name="px-4 py-2 text-white bg-accent hover:bg-accent-hover rounded-lg w-full cursor-pointer"
    ></u-submit-button>
  </u-signin-step>

  <!-- Shown when the tenant requires profile fields the account is missing -->
  <u-signin-step name="missing-fields">
    <p class="mb-3 text-center text-sm text-text-light">One last step — complete your profile</p>
    <u-missing-field></u-missing-field>
    <u-missing-fields-submit-button></u-missing-fields-submit-button>
  </u-signin-step>

  <!-- Unknown email → the flow switches to this step -->
  <u-signin-step name="registration">
    <u-back-button class-name="mb-3 text-sm text-primary cursor-pointer">← Back</u-back-button>
    <p class="text-sm text-text-light">
      No account found for this email. See the
      <a href="/registration" class="text-primary underline">registration flow demo</a>
      for the full multi-step sign-up.
    </p>
  </u-signin-step>
</u-signin-root>

<u-signed-in>
  <div class="rounded-lg bg-success-bg p-4 text-sm text-success-text">
    You are signed in — the sign-in form hides itself automatically.
  </div>
</u-signed-in>
Live demo
← Back Set a password for this account

Enter the code we sent to your email

Choose a new password

One last step — complete your profile

← Back

No account found for this email. See the registration flow demo for the full multi-step sign-up.

You are signed in — the sign-in form hides itself automatically.

Social login

u-social-login-button starts the provider's OAuth flow in a Unidy-hosted dialog. Providers are configured per tenant; custom providers and custom button icons are supported via the provider attribute and icon slot.

  • Google, LinkedIn, Apple, … — one attribute per provider
  • Custom providers — any OAuth provider configured in your tenant
  • Theming — light/dark themes, icon-only mode, icon slot
<!-- Placed directly inside u-signin-root (outside any step), the buttons are
     always available while the user is signed out -->
<u-signin-root class="flex flex-col gap-3">
  <u-social-login-button text="Continue with Google" provider="google" theme="dark"
  ></u-social-login-button>
  <u-social-login-button text="Continue with LinkedIn" provider="linkedin"></u-social-login-button>
  <u-social-login-button text="Continue with Apple" provider="apple" theme="dark"
  ></u-social-login-button>

  <!-- Custom provider configured in your Unidy tenant -->
  <u-social-login-button text="Continue with Some Provider" provider="some-provider"
  ></u-social-login-button>

  <!-- Custom icon via the icon slot -->
  <u-social-login-button text="Continue with Unidy" provider="unidy" theme="dark" icon-only="true">
    <img
      slot="icon"
      src="https://www.unidy.io/unidy-logo-white.svg"
      alt="Unidy Logo"
      class="w-20 h-6"
    />
  </u-social-login-button>
</u-signin-root>

<u-signed-in>
  <p class="text-sm text-success-text">Signed in — social login buttons are hidden.</p>
</u-signed-in>
Live demo
Unidy Logo

Signed in — social login buttons are hidden.

Passkey sign-in

u-passkey performs WebAuthn discoverable-credential sign-in: the browser offers stored passkeys, no email entry required. Enrollment happens during registration (see the registration demo) or in the Unidy account UI.

  • One-tap sign-in — Face ID, fingerprint, or device PIN
  • Discoverable credentials — works without typing an email first
<!-- Discoverable passkey sign-in: no email needed, the browser offers stored passkeys -->
<u-signin-root class="flex flex-col gap-2">
  <u-passkey
    text="Sign in with passkey"
    loading-text="Authenticating…"
    class-name="px-4 py-2 text-white bg-dark hover:bg-dark-lighter rounded-lg disabled:opacity-50 w-full cursor-pointer transition-colors"
  ></u-passkey>
  <u-error-message for="passkey" class-name="mt-2 text-sm text-danger"></u-error-message>
</u-signin-root>

<u-signed-in>
  <p class="text-sm text-success-text">
    Signed in — enroll passkeys during registration or from the Unidy account page.
  </p>
</u-signed-in>
Live demo

Signed in — enroll passkeys during registration or from the Unidy account page.

Auth events

Every sign-in root emits DOM events you can wire up with plain addEventListener: authEvent on successful sign-in, errorEvent on failures, and logout from u-logout-button. This is how you sync your own UI (header state, redirects, analytics) with the SDK.

  • authEvent / errorEvent / logout — plain DOM events, no SDK API needed
  • Live event log — sign in above and watch the events arrive
<div class="rounded-lg border border-border bg-background-light p-4">
  <p class="mb-2 text-sm font-medium">Event log</p>
  <ul id="auth-event-log" class="flex flex-col gap-1 font-mono text-xs text-text-light">
    <li>Waiting for auth events… (use the sign-in flow above)</li>
  </ul>
</div>

<script is:inline>
  (() => {
    const log = (message) => {
      const entry = document.createElement("li");
      entry.textContent = `${new Date().toLocaleTimeString()} ${message}`;
      document.getElementById("auth-event-log")?.prepend(entry);
    };

    // Events bubble from any u-signin-root on the page
    document.querySelectorAll("u-signin-root").forEach((root) => {
      root.addEventListener("authEvent", (event) => {
        log(`authEvent — user signed in (${JSON.stringify(event.detail ?? {})})`);
      });
      root.addEventListener("errorEvent", (event) => {
        log(`errorEvent — ${event.detail?.error ?? "unknown error"}`);
      });
    });

    // Logout is emitted by u-logout-button
    document.querySelectorAll("u-logout-button").forEach((button) => {
      button.addEventListener("logout", () => log("logout"));
    });
  })();
</script>
Live demo

Event log

  • Waiting for auth events… (use the sign-in flow above)