Documentation
Build withFramework quickstartsHTML

HTML

A working sign-in in one file. No bundler, no framework, no build.

The SDK is an ES module, so a browser can load it directly. Useful for a landing page, a prototype, or an app that has no bundler.

Load it from a CDN

<script type="module">
  import { createClient, Storage } from 'https://cdn.nulvless.com/js@1/index.min.js';
</script>

Pin the major version. @1 receives fixes without breaking changes.

The whole thing

index.html
<!doctype html>
<html lang="en">
  <body>
    <form id="sign-in">
      <input name="email" type="email" placeholder="[email protected]" required />
      <input name="password" type="password" required />
      <button type="submit">Sign in</button>
      <p id="error" role="alert" hidden></p>
    </form>

    <div id="signed-in" hidden>
      <p id="who"></p>
      <button id="sign-out">Sign out</button>
    </div>

    <script type="module">
      import { createClient, Storage } from 'https://cdn.nulvless.com/js@1/index.min.js';

      const nulvless = createClient(
        'https://<project>.nulvless.co',
        'pk-…',
        { storage: Storage.Local },
      );

      const form = document.getElementById('sign-in');
      const signedIn = document.getElementById('signed-in');
      const error = document.getElementById('error');

      async function render() {
        const { data: session } = await nulvless.auth.getSession();

        form.hidden = Boolean(session);
        signedIn.hidden = !session;

        if (session) {
          document.getElementById('who').textContent = session.user.email;
        }
      }

      form.addEventListener('submit', async (event) => {
        event.preventDefault();
        error.hidden = true;

        const body = new FormData(form);
        const { data, error: signInError } = await nulvless.auth.signIn({
          email: body.get('email'),
          password: body.get('password'),
        });

        if (signInError) {
          error.textContent = 'Check your email and password.';
          error.hidden = false;
          return;
        }

        if (data.challenge) {
          // Collect the code and call auth.verifyChallenge({ token, code })
          return;
        }

        render();
      });

      document.getElementById('sign-out').addEventListener('click', async () => {
        await nulvless.auth.signOut();
        render();
      });

      render();
    </script>
  </body>
</html>

Social sign-in

document.getElementById('google').addEventListener('click', () => {
  nulvless.auth.authorize('google', {
    redirect_uri: window.location.origin + '/callback.html',
  });
});

On callback.html:

const code = new URLSearchParams(location.search).get('code');
if (code) {
  await nulvless.auth.exchangeCodeForToken(code);
  location.href = '/';
}

The redirect URI has to be on the project's allowlist, or the platform rejects it before any token exists.

What to watch for

Publishable key only

Everything on this page runs in the browser, so only a pk- key belongs here. A sk- key in a script tag is readable by anyone who opens the page.

Without a framework nothing re-renders on its own, so subscribe to the events if the session can change while the page is open:

nulvless.auth.on('signed-out', render);
nulvless.auth.on('unauthorized', render);

On this page