> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rhinestone.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Sign up and sign in

> Authenticate a user with a passkey and use the resulting smart account.

export const CSS = ".rhinestone-1auth-demo .oa-demo {\n  margin: 1.5rem 0;\n  border: 1px solid #202127;\n  border-radius: 16px;\n  background: #0b0c0e;\n  overflow: hidden;\n}\n.rhinestone-1auth-demo .oa-demo-head {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  min-height: 39px;\n  padding: 0.625rem 1rem;\n  border-bottom: 1px solid #191a1e;\n}\n.rhinestone-1auth-demo .oa-demo-title {\n  font-size: 0.8125rem;\n  font-weight: 500;\n  letter-spacing: -0.01em;\n  color: #e4e4e9;\n}\n.rhinestone-1auth-demo .oa-demo-status {\n  display: flex;\n  align-items: center;\n  gap: 0.4375rem;\n  flex: 0 0 auto;\n  font-size: 0.75rem;\n  color: #5e5e68;\n}\n.rhinestone-1auth-demo .oa-demo-dot {\n  width: 6px;\n  height: 6px;\n  border-radius: 999px;\n  background: #3f3f48;\n}\n.rhinestone-1auth-demo .oa-demo-dot[data-state=\"on\"] {\n  background: #22c55e;\n  box-shadow: 0 0 8px rgba(34, 197, 94, 0.5);\n}\n.rhinestone-1auth-demo .oa-demo-body {\n  display: flex;\n  flex-direction: column;\n  gap: 0.75rem;\n  padding: 1rem;\n}\n.rhinestone-1auth-demo .demo-preview {\n  display: contents;\n}\n.rhinestone-1auth-demo .demo-preview-header {\n  display: none;\n}\n.rhinestone-1auth-demo .demo-preview-content {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  gap: 0.75rem;\n}\n.rhinestone-1auth-demo .oa-demo-body > * > [style] {\n  margin: 0 !important;\n}\n.rhinestone-1auth-demo .oa-demo-body > * > div[style]:not([class]) {\n  padding: 0 !important;\n}\n.rhinestone-1auth-demo .oa-demo-body [style] {\n  text-align: left !important;\n}\n.rhinestone-1auth-demo .oa-demo-body [style*=\"justify-content: center\"] {\n  justify-content: flex-start !important;\n}\n.rhinestone-1auth-demo .demo-preview-content > div[style]:not([class]) {\n  padding: 0.75rem 0.875rem !important;\n  border-radius: 10px !important;\n  background: #14151a !important;\n  color: #8a8a94 !important;\n  font-size: 0.8125rem !important;\n}\n.rhinestone-1auth-demo .demo-preview-button {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.25rem;\n  padding: 0 0.875rem;\n  border: none;\n  border-radius: 10px;\n  background: #4f41ef;\n  color: #ffffff;\n  font-size: 0.875rem;\n  font-weight: 500;\n  cursor: pointer;\n  transition:\n    background 0.15s ease,\n    opacity 0.15s ease;\n}\n.rhinestone-1auth-demo .demo-preview-button:hover:not(:disabled) {\n  background: #6355f5;\n}\n.rhinestone-1auth-demo .demo-preview-button:disabled {\n  opacity: 0.6;\n  cursor: default;\n}\n.rhinestone-1auth-demo .oa-demo-body input[type=\"checkbox\"] {\n  accent-color: #4f41ef;\n}\n.rhinestone-1auth-demo .demo-preview-signout {\n  padding: 0 0.125rem;\n  border: none;\n  background: transparent;\n  color: #75757f;\n  font-size: 0.8125rem;\n  font-weight: 500;\n  cursor: pointer;\n  transition: color 0.15s ease;\n}\n.rhinestone-1auth-demo .demo-preview-signout:hover {\n  color: #f87171;\n}\n.rhinestone-1auth-demo .demo-preview-address {\n  padding: 0.25rem 0.5rem;\n  border: 1px solid #24252b;\n  border-radius: 6px;\n  background: #16171c;\n  font-family: paperMono, ui-monospace, SFMono-Regular, Menlo, monospace;\n  font-size: 0.75rem;\n  color: #d4d4da;\n}\n.rhinestone-1auth-demo .demo-error {\n  margin: 0;\n  font-size: 0.8125rem;\n  color: #f87171;\n  overflow-wrap: anywhere;\n}\n.rhinestone-1auth-demo .oa-demo-skeleton {\n  display: block;\n  width: 7rem;\n  height: 2.25rem;\n  border-radius: 10px;\n  background: #16171c;\n  animation: rhinestone-1auth-demo-pulse 1.6s ease-in-out infinite;\n}\n@keyframes rhinestone-1auth-demo-pulse {\n  50% {\n    opacity: 0.45;\n  }\n}";

export const SOURCE_COMMIT = "e06ebe8add704e95f679eaddc38fd7ea88cb3de8";

export const SDK_VERSION = "0.10.1";

export const VIEM_VERSION = "2.55.0";

export const STORAGE_KEY = "rhinestone-docs-1auth-account";

export const PROD_BACKENDS = {
  auth: "https://passkey.1auth.app",
  sponsorship: "https://docs.1auth.app/api/sponsorship"
};

export const DEV_BACKENDS = {
  auth: "https://passkey.passkey-test.app",
  sponsorship: "https://docs.passkey-test.app/api/sponsorship"
};

export const PROD_DOCS_HOST = "docs.rhinestone.dev";

export const backends = () => {
  const host = typeof window === "undefined" ? "" : window.location.hostname;
  return host === PROD_DOCS_HOST ? PROD_BACKENDS : DEV_BACKENDS;
};

export const BACKEND_NOTE = "Only the production docs host talks to the production Rhinestone deployment. " + "Every other host — a Mintlify preview, a tunnel, localhost — uses dev, so " + "an unrecognised preview pattern fails towards dev rather than creating " + "passkey state against production.";

export const CDN = spec => "https://esm.sh/" + spec + (spec.startsWith("@rhinestone/1auth@") ? "?deps=viem@" + VIEM_VERSION + "&external=react" : "");

export const shareReact = () => {
  if (window.__OA_SHARED_REACT__) return;
  if (typeof HTMLScriptElement.supports !== "function" || !HTMLScriptElement.supports("importmap")) {
    throw new Error("This demo requires a browser with import-map support.");
  }
  window.__OA_REACT__ = React;
  const reserved = ["default", "import", "export", "class", "function", "const", "let", "var", "new", "delete", "typeof", "void", "in", "of", "do", "if", "else", "for", "while", "switch", "case", "break", "continue", "return", "this", "super", "throw", "try", "catch", "finally", "yield", "await", "enum", "null", "true", "false", "with", "debugger", "instanceof", "extends", "static"];
  const names = Object.keys(React).filter(name => (/^[A-Za-z_$][A-Za-z0-9_$]*$/).test(name) && !reserved.includes(name));
  const source = ["const R = globalThis.__OA_REACT__; export default R;", ...names.map(name => "export const " + name + " = R." + name + ";")].join(" ");
  const jsx = ["const R = globalThis.__OA_REACT__; export const Fragment = R.Fragment;", "export const jsx = (type, props, key) => R.createElement(type, key == null ? props : { ...props, key });", "export const jsxs = jsx; export const jsxDEV = jsx;"].join(" ");
  const dataUrl = value => "data:text/javascript," + encodeURIComponent(value);
  const script = document.createElement("script");
  script.type = "importmap";
  script.textContent = JSON.stringify({
    imports: {
      react: dataUrl(source),
      "react/jsx-runtime": dataUrl(jsx),
      "react/jsx-dev-runtime": dataUrl(jsx)
    }
  });
  document.head.appendChild(script);
  window.__OA_SHARED_REACT__ = true;
};

export const MODULE_SPECS = {
  sdk: () => CDN("@rhinestone/1auth@" + SDK_VERSION)
};

export const importModule = url => new Promise((resolve, reject) => {
  const key = "__oa_" + Math.random().toString(36).slice(2);
  window[key] = {
    resolve,
    reject
  };
  const script = document.createElement("script");
  script.type = "module";
  script.textContent = "import(" + JSON.stringify(url) + ").then(m => window[" + JSON.stringify(key) + "].resolve(m))" + ".catch(e => window[" + JSON.stringify(key) + "].reject(new Error(String(e))));";
  script.onerror = () => reject(new Error("failed to load " + url));
  document.head.appendChild(script);
  script.remove();
});

export const importModuleWithRetry = async url => {
  try {
    return await importModule(url);
  } catch (first) {
    await new Promise(r => setTimeout(r, 500));
    return importModule(url);
  }
};

export const loadDeps = async () => {
  shareReact();
  const specs = Object.entries(MODULE_SPECS).map(([name, spec]) => [name, spec()]);
  const cacheKey = "__OA_DEPS__" + specs.map(([, url]) => url).join("|");
  if (window[cacheKey]) return window[cacheKey];
  const loaded = await Promise.all(specs.map(([, url]) => importModuleWithRetry(url)));
  const deps = {
    React
  };
  specs.forEach(([name], i) => {
    deps[name] = loaded[i];
  });
  deps.motion = motionShim();
  window[cacheKey] = deps;
  return deps;
};

export const unreachable = call => "Sponsorship " + call + " never reached " + backends().sponsorship + ". The browser hides why: a rejected origin, a failed preflight and a network" + " error all look identical here. The failing request is in the console.";

export const sponsorship = {
  accessToken: async () => {
    let res;
    try {
      res = await fetch(backends().sponsorship + "/access-token", {
        method: "GET",
        credentials: "include"
      });
    } catch (e) {
      throw new Error(unreachable("access token"));
    }
    if (!res.ok) throw new Error("Sponsorship access token failed (" + res.status + ")");
    return (await res.json()).token;
  },
  getExtensionToken: async intentOp => {
    let res;
    try {
      res = await fetch(backends().sponsorship + "/extension-token", {
        method: "POST",
        credentials: "include",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          intentOp
        })
      });
    } catch (e) {
      throw new Error(unreachable("extension token"));
    }
    if (!res.ok) throw new Error("Sponsorship extension token failed (" + res.status + ")");
    return (await res.json()).token;
  }
};

export const motionShim = () => {
  const passthrough = Tag => React.forwardRef((props, ref) => {
    const rest = Object.fromEntries(Object.entries(props).filter(([k]) => !["initial", "animate", "exit", "transition", "variants", "whileHover", "whileTap", "layout", "layoutId"].includes(k)));
    return React.createElement(Tag, {
      ...rest,
      ref
    });
  });
  const cache = {};
  return {
    motion: new Proxy({}, {
      get: (_, tag) => {
        if (!cache[tag]) cache[tag] = passthrough(tag);
        return cache[tag];
      }
    }),
    AnimatePresence: ({children}) => React.createElement(React.Fragment, null, children),
    useReducedMotion: () => true
  };
};

export const AuthContext = React.createContext(null);

export const useAuthContext = () => React.useContext(AuthContext);

export const buildModules = deps => {
  const mods = {};
  mods.authctx = (() => {
    const DOCS_APP_ORIGIN_STORAGE_KEY = STORAGE_KEY;
    const useAuth = () => useAuthContext();
    return {
      DOCS_APP_ORIGIN_STORAGE_KEY,
      useAuth,
      clearDocsSession: () => {}
    };
  })();
  mods.authDemo = (() => {
    const {useState} = deps.React;
    const {useAuth} = mods.authctx;
    function AuthDemo() {
      const {address, isAuthenticated, login, logout} = useAuth();
      const [error, setError] = useState(null);
      const [loading, setLoading] = useState(false);
      const handleAuth = async () => {
        setLoading(true);
        setError(null);
        try {
          await login();
        } catch (err) {
          setError(err instanceof Error ? err.message : "Unknown error");
        } finally {
          setLoading(false);
        }
      };
      const handleDisconnect = () => {
        logout();
        setError(null);
      };
      return React.createElement("div", {
        className: "demo-preview"
      }, React.createElement("div", {
        className: "demo-preview-header"
      }, "Preview"), React.createElement("div", {
        className: "demo-preview-content"
      }, !isAuthenticated ? React.createElement("button", {
        onClick: handleAuth,
        disabled: loading,
        className: "demo-preview-button"
      }, loading ? "Connecting..." : "Sign in") : React.createElement(React.Fragment, null, React.createElement("span", {
        className: "demo-preview-address"
      }, address && `${address.slice(0, 6)}\u2026${address.slice(-4)}`), React.createElement("button", {
        onClick: handleDisconnect,
        className: "demo-preview-signout"
      }, "Sign out"))), isAuthenticated && React.createElement("div", {
        style: {
          padding: "0 1rem 1rem",
          fontSize: "0.75rem",
          color: "#22c55e"
        }
      }, "WebAuthn is bound to this docs hostname."), error && React.createElement("div", {
        style: {
          padding: "0 1rem 1rem",
          fontSize: "0.75rem",
          color: "#ef4444"
        }
      }, "Error: ", error));
    }
    return {
      AuthDemo
    };
  })();
  return mods;
};

export const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;

export const readStoredAddressFromJsonEnvelope = () => {
  try {
    const raw = window.localStorage.getItem(STORAGE_KEY);
    if (!raw) return null;
    const parsed = JSON.parse(raw);
    const address = parsed && typeof parsed === "object" ? parsed.address : null;
    return typeof address === "string" && ADDRESS_RE.test(address) ? address : null;
  } catch {
    return null;
  }
};

export const writeStoredAddressAsJsonEnvelope = address => {
  window.localStorage.setItem(STORAGE_KEY, JSON.stringify({
    address
  }));
};

export const useOneAuth = deps => {
  const [address, setAddress] = React.useState(null);
  const [client, setClient] = React.useState(null);
  const mounted = React.useRef(false);
  const sessionGeneration = React.useRef(0);
  React.useEffect(() => {
    if (!deps) return;
    mounted.current = true;
    const restoreGeneration = ++sessionGeneration.current;
    const {OneAuthClient} = deps.sdk;
    const nextClient = new OneAuthClient({
      providerUrl: backends().auth,
      clientId: "docs-app-origin",
      webauthn: {
        mode: "app_origin",
        rpId: window.location.hostname
      },
      theme: {
        mode: "dark",
        accent: "#ffffff",
        backdrop: {
          color: "#52525c",
          opacity: 0.5,
          blur: 12
        }
      },
      experimental_clear_signing: true,
      recovery: {
        fallback: {
          type: "verified-identity"
        }
      },
      testnets: true,
      prewarm: true,
      sponsorship,
      onDisconnect: () => {
        if (!mounted.current) return;
        sessionGeneration.current += 1;
        window.localStorage.removeItem(STORAGE_KEY);
        setAddress(null);
      }
    });
    setClient(nextClient);
    const stored = readStoredAddressFromJsonEnvelope();
    if (stored) {
      nextClient.getSession().then(result => {
        if (!mounted.current || sessionGeneration.current !== restoreGeneration) return;
        const account = result.success ? result.session.accountAddress : null;
        if (account && ADDRESS_RE.test(account)) {
          writeStoredAddressAsJsonEnvelope(account);
          setAddress(account);
        } else {
          window.localStorage.removeItem(STORAGE_KEY);
          setAddress(null);
        }
      }).catch(() => {
        if (!mounted.current || sessionGeneration.current !== restoreGeneration) return;
        window.localStorage.removeItem(STORAGE_KEY);
        setAddress(null);
      });
    }
    return () => {
      mounted.current = false;
      sessionGeneration.current += 1;
    };
  }, [deps]);
  const login = React.useCallback(async () => {
    if (!client) return null;
    const generation = ++sessionGeneration.current;
    const result = await client.authenticate({
      oauthEnabled: false
    });
    if (!result.success) {
      if (result.error?.code === "USER_CANCELLED") return null;
      throw new Error(result.error?.message ?? "Authentication failed");
    }
    const account = result.session.accountAddress;
    if (!ADDRESS_RE.test(account)) throw new Error("Authentication returned an invalid account address");
    if (!mounted.current || sessionGeneration.current !== generation) return null;
    writeStoredAddressAsJsonEnvelope(account);
    setAddress(account);
    return account;
  }, [client]);
  const logout = React.useCallback(() => {
    sessionGeneration.current += 1;
    window.localStorage.removeItem(STORAGE_KEY);
    if (mounted.current) setAddress(null);
    if (client?.disconnect) client.disconnect();
  }, [client]);
  return React.useMemo(() => ({
    address,
    isAuthenticated: !!address,
    client,
    sponsorship,
    login,
    logout
  }), [address, client, login, logout]);
};

export const Frame = ({children, connected}) => <div className="oa-demo">
    <div className="oa-demo-head">
      <span className="oa-demo-title">Sign in with a passkey</span>
      <span className="oa-demo-status">
        <span className="oa-demo-dot" data-state={connected ? "on" : "off"} />
        {connected ? "Signed in" : "Not connected"}
      </span>
    </div>
    <div className="oa-demo-body">{children}</div>
  </div>;

export const AuthDemo = () => {
  const [deps, setDeps] = React.useState(null);
  const [loadError, setLoadError] = React.useState("");
  const [connecting, setConnecting] = React.useState(false);
  React.useEffect(() => {
    let cancelled = false;
    loadDeps().then(d => !cancelled && setDeps(d)).catch(e => !cancelled && setLoadError(String(e.message ?? e)));
    return () => {
      cancelled = true;
    };
  }, []);
  const mods = React.useMemo(() => deps ? buildModules(deps) : null, [deps]);
  const auth = useOneAuth(deps);
  return <div className="not-prose rhinestone-1auth-demo" data-demo="auth">
      <style dangerouslySetInnerHTML={{
    __html: CSS
  }} />
      <AuthContext.Provider value={auth}>
        <Frame connected={auth.isAuthenticated}>
          {mods && auth.client && false && !auth.isAuthenticated ? <div>
              <button className="demo-button" disabled={connecting} onClick={async () => {
    setConnecting(true);
    setLoadError("");
    try {
      await auth.login();
    } catch (error) {
      setLoadError(String(error.message ?? error));
    } finally {
      setConnecting(false);
    }
  }}>{connecting ? "Signing in..." : "Sign in to try it"}</button>
              {loadError ? <p className="demo-error">{loadError}</p> : null}
            </div> : mods && (auth.client || false) ? React.createElement(mods.authDemo.AuthDemo, {
    key: auth.address
  }) : loadError ? <p className="demo-error">{loadError}</p> : <span className="oa-demo-skeleton" />}
        </Frame>
      </AuthContext.Provider>
    </div>;
};

Use `authenticate()` for both new and returning users. Start with the shared [client initialization](/wallets/embedded-wallets/accounts#initialize-the-client).

<AuthDemo />

## Authenticate

```ts theme={null}
import { oneAuth } from "./oneauth"

const result = await oneAuth.authenticate()

if (!result.success) {
  if (result.error.code === "USER_CANCELLED") return
  throw new Error(result.error.message)
}

const { accountAddress } = result.session
```

The flow proceeds in four stages:

1. The user verifies an email or OAuth identity in the wallet dialog.
2. The service checks for an account in your application's smart-account namespace.
3. WebAuthn creates or uses a passkey in your application's top-level page.
4. The SDK returns the account address and session metadata.

The service decides whether the account needs registration, login, or recovery. You can pass `flow: "create-account"` or `flow: "login"` to select the initial presentation, but these values are hints rather than authorization boundaries:

```ts theme={null}
await oneAuth.authenticate({ flow: "create-account" })
await oneAuth.authenticate({ flow: "login" })
```

Enforce invite lists or other application eligibility rules before opening the dialog.

## Create an application session

A successful `authenticate()` result proves control of the embedded wallet session. It does not create a session for your own backend. If your application needs one, issue a single-use nonce, include your domain and expiry in a message, then ask the account to sign it:

```ts theme={null}
const message = [
  "Sign in to My App",
  `Domain: ${window.location.host}`,
  `Nonce: ${nonceFromServer}`,
  `Expires: ${expiresAt}`,
].join("\n")

import { encodeWebAuthnSignature } from "@rhinestone/1auth"

const signed = await oneAuth.signMessage({
  accountAddress,
  message,
  description: "Verify your identity",
})

if (!signed.success) {
  throw new Error(signed.error?.message ?? "Signing failed")
}

if (!signed.signature) {
  throw new Error("Signing did not return a WebAuthn signature")
}

const signature = encodeWebAuthnSignature(signed.signature)
```

Send the exact message, signature, and account address to your backend. Verify the smart-account signature there, consume the nonce once, enforce the expiry and domain, then issue your application session. Use a verifier that supports ERC-1271 smart-account signatures.

## Sign out

```ts theme={null}
await oneAuth.disconnect()
```

Also clear your application's server session and in-memory user state. Signing out of one application does not sign the user out of another application namespace.

## Error handling

Treat structured error codes as control flow. `USER_CANCELLED` is an expected dismissal; configuration and origin errors require an integration fix. See [domain troubleshooting](/wallets/embedded-wallets/passkeys-and-domains#troubleshoot-domain-configuration) for checks before retrying.
