# Unidy Web SDK — 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.

## Setup

Every example below assumes the SDK is loaded and configured once per page:

```html
<script type="module" src="https://cdn.jsdelivr.net/npm/@unidy.io/sdk@1.8.1/dist/sdk/sdk.esm.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@unidy.io/sdk@1.8.1/dist/sdk/sdk.css">

<u-config
  base-url="https://hammarby.staging.unidy.app"
  api-key="your-api-key"
  locale="en"
  check-signed-in="true"></u-config>
```

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

```html
<!-- 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: /auth#sign-in-flow

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

```html
<!-- 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: /auth#social-login

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

```html
<!-- 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: /auth#passkey

## Modal login

The same sign-in components work inside any dialog you own. A dozen lines of vanilla JS open/close the modal; listening to the authEvent closes it on success.

- **Your modal, SDK inside** — no special modal component needed
- **authEvent-driven UX** — close the dialog the moment sign-in succeeds

```html
<!-- Only offer the modal to signed-out visitors — the sign-in root inside
     would render empty for an authenticated user -->
<u-signed-in not>
  <button id="open-login-modal" class="btn btn-primary">Sign in</button>
</u-signed-in>

<div id="login-modal" class="fixed inset-0 z-50 hidden items-center justify-center">
  <div class="absolute inset-0 bg-black/50 backdrop-blur-sm" data-close-modal></div>

  <div class="relative z-10 w-full max-w-md rounded-xl bg-white p-6 shadow-2xl">
    <button
      data-close-modal
      aria-label="Close"
      class="absolute right-4 top-3 text-2xl text-text-muted hover:text-text">×</button
    >

    <u-signin-root id="modal-signin" class="block w-full">
      <u-signin-step name="email">
        <h2 class="mb-1 text-center text-xl font-semibold">Welcome</h2>
        <p class="mb-4 text-center text-sm text-text-light">Sign in or create an account</p>

        <u-social-login-button provider="google" theme="dark"></u-social-login-button>

        <div class="my-4 flex items-center">
          <div class="flex-grow border-t border-border"></div>
          <span class="mx-3 text-xs font-medium text-text-muted">OR</span>
          <div class="flex-grow border-t border-border"></div>
        </div>

        <u-email-field 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"
          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-signin-step>

      <u-signin-step name="verification">
        <u-back-button class-name="mb-3 text-sm text-primary cursor-pointer">← Back</u-back-button>
        <u-password-field
          placeholder="Enter your password"
          class-name="px-4 py-2 mb-2 border border-border rounded-lg w-full"></u-password-field>
        <u-error-message for="password" 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-signin-step>
    </u-signin-root>
  </div>
</div>

<u-signed-in>
  <p class="mt-3 text-sm text-success-text">Signed in — the modal closes on the authEvent.</p>
</u-signed-in>

<script is:inline>
  (() => {
    const modal = document.getElementById("login-modal");
    const open = () => {
      modal.classList.remove("hidden");
      modal.classList.add("flex");
    };
    const close = () => {
      modal.classList.add("hidden");
      modal.classList.remove("flex");
    };

    document.getElementById("open-login-modal")?.addEventListener("click", open);
    modal
      .querySelectorAll("[data-close-modal]")
      .forEach((el) => el.addEventListener("click", close));
    document.addEventListener("keydown", (e) => e.key === "Escape" && close());

    // Close the modal as soon as the user is authenticated
    document.getElementById("modal-signin")?.addEventListener("authEvent", close);
  })();
</script>
```

Live demo: /auth#modal-login

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

```html
<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: /auth#auth-events

## Further reading

- Full component reference: https://github.com/UnidyID/unidy-sdk/blob/HEAD/packages/sdk/readme.md
- Official quick-start examples: https://github.com/UnidyID/unidy-sdk/blob/HEAD/packages/sdk/quick-start-examples.md
- Package: https://www.npmjs.com/package/@unidy.io/sdk
- All guides: /llms.txt
