View as Markdown

Visitor Accounts

What are Visitor Accounts?

Visitor Accounts lets your site's visitors create accounts, sign in, stay signed in across page loads, verify their email, and reset forgotten passwords — all on your own domain, with no authentication backend to build or maintain. Ezoic hosts the account system; you add a small script and either a drop-in form or your own custom UI powered by the window.ezAuth JavaScript API.

Visitor accounts are isolated per site: an account created on your domain exists only for your domain.

Supported sign-in methods:

  • Email + password — works out of the box, no provider setup.
  • Google sign-in — server-verified Google Identity Services login, available once you configure a Google client ID in your dashboard.
Visitor Accounts also powers visitor sign-in for Ezoic Subscriptions. If you use the Subscriptions widget, it loads and reuses the same window.ezAuth integration automatically.

The Dashboard

In your Ezoic dashboard, go to Identity → Visitor Accounts. The page shows account activity for the selected site:

  • Total accounts and verified accounts
  • New signups over the last 7 and 30 days
  • Logins over the last 7 and 30 days
  • A 30-day daily signups chart
  • Export accounts (CSV) — download your site's visitor accounts, including account ID, email, status, verification state, and creation/update timestamps

The same page links to the login provider settings where you can enable Google sign-in, and shows the copy-paste integration snippets below pre-filled with your site's ID.

Quick Start

Step 1 — Add the SDK

Add this once, near the top of <head>, on every page where visitors can sign in or need to be recognized. Replace YOUR_DOMAIN_ID with your Ezoic site ID (shown pre-filled on the Visitor Accounts dashboard page):

<!-- Ezoic Identity (ezAuth) — add once, near the top of <head> -->
<script>window.ezAuthConfig = { domainId: YOUR_DOMAIN_ID };</script>
<script src="https://www.ezojs.com/detroitchicago/ferndale.js"></script>

The script publishes window.ezAuth and automatically restores any persisted login, so returning visitors are signed in on every page load.

You can also configure via a script-tag attribute instead of the global:

<script src="https://www.ezojs.com/detroitchicago/ferndale.js" data-ez-domain-id="YOUR_DOMAIN_ID"></script>
Pages using the SDK must be served from the domain configured with Ezoic. Requests from other origins are rejected with an origin_mismatch error.

Step 2 — Drop in the form

The fastest integration is Ezoic's built-in sign-in / create-account form. Add an element and mount the form into it:

<!-- Drop-in sign-in / create-account form -->
<div id="ezauth-form"></div>
<script>window.ezAuth.mount("#ezauth-form");</script>

The form handles login, registration, forgotten passwords, password reset, and email verification. When a visitor lands from an emailed reset or verification link, the form automatically shows the right screen.

mount(target, options) accepts an optional second argument:

  • mode — initial view, "login" (default) or "register"
  • showToggle — whether the form shows the link to switch between sign-in and create-account (default true)

It returns a handle with an unmount() method.

Styling the form

The form ships unstyled on purpose — it emits semantic markup with a stable set of CSS classes so it inherits your site's look. Add your own stylesheet targeting those classes.

The mounted root is <div class="ezauth ezauth--<screen>">, where the modifier class reflects the current screen (ezauth--login, ezauth--register, ezauth--forgot, ezauth--reset, ezauth--verify, ezauth--signed-in, ezauth--notice). Every button carries ezauth__button and every text input carries ezauth__input, plus a specific class per control (ezauth__submit, ezauth__email, ezauth__password, ezauth__toggle, ezauth__forgot-link, ezauth__back, ezauth__logout), with ezauth__form, ezauth__heading, ezauth__field, ezauth__label, and ezauth__message (inline errors) for structure. These class names are a stable contract and will not change.

A minimal starting point:

.ezauth {
  max-width: 360px;
  margin: 0 auto;
  padding: 24px;
  border: 1px solid #d2d6dc;
  border-radius: 8px;
}

.ezauth__form { display: flex; flex-direction: column; gap: 14px; }
.ezauth__input { width: 100%; padding: 10px 12px; border: 1px solid #d2d6dc; border-radius: 8px; }
.ezauth__button { border: none; border-radius: 8px; padding: 10px 14px; cursor: pointer; }
.ezauth__submit { background: #2b6cb0; color: #ffffff; font-weight: 600; }
.ezauth__toggle, .ezauth__forgot-link, .ezauth__back { background: transparent; color: #2b6cb0; padding: 4px 0; text-align: left; }
.ezauth__message { color: #c53030; font-size: 0.85rem; }
.ezauth__message:empty { display: none; }

Scope your rules to .ezauth so they cannot leak into the rest of your page. Mounting multiple forms on one page is supported — the classes are shared, so one stylesheet styles them all.

Step 3 (optional) — Build a custom UI

For full control, skip the drop-in form and drive authentication from JavaScript:

// React to sign-in / sign-out anywhere on your site.
window.ezAuth.onChange(function (state) {
  if (state.status === "authenticated") {
    console.log("signed in as account #" + state.user.accountId);
  }
});

// Email + password.
window.ezAuth.register("visitor@example.com", "their-password");
window.ezAuth.login("visitor@example.com", "their-password");
window.ezAuth.logout();      // this device
window.ezAuth.logoutAll();   // every device

// Forgotten password (the SDK reads the emailed token off the landing URL).
window.ezAuth.forgotPassword("visitor@example.com");

JavaScript API Reference

All methods live on window.ezAuth. The SDK initializes itself from your embed config; every action method rejects with a not_initialized error if called before configuration.

Reading state

Method Returns Description
isAuthenticated() boolean Whether a visitor is signed in.
getUser() { accountId, emailVerified } or null The signed-in visitor. Token-free — no email address is exposed.
getState() { status, user } status is "authenticated" or "anonymous".
onChange(listener) unsubscribe function Subscribes to auth-state changes. Fires immediately with the current state, then on every change. Safe to call before the SDK initializes.
isInitialized() boolean Whether the SDK has been configured.
getDomainId() number or null The configured site ID.

Email + password

Method Returns Description
register(email, password) Promise<user> Creates an account and signs the visitor in. A verification email is sent automatically.
login(email, password) Promise<user> Signs the visitor in.
logout() Promise<void> Signs out on this device.
logoutAll() Promise<number> Signs out everywhere; resolves with the number of sessions revoked.

Sessions

Sessions persist in the browser and are restored automatically on page load — you normally never call these directly:

Method Returns Description
restore() Promise<user | null> Restores a persisted session, if any.
refresh() Promise<user | null> Forces a session refresh.
getAccessToken() Promise<string | null> The visitor's current access token (refreshed automatically), or null when signed out. Useful when your own code needs to identify the visitor's session.

Google sign-in

Google sign-in is server-verified: your page obtains an ID token from Google Identity Services (GIS) and hands it to the SDK, which verifies it with Ezoic's servers before issuing a session.

Method Returns Description
getOAuthConfig() Promise<{ google: { enabled, clientId } }> Which providers are configured for your site — use it to decide whether to render a Google button. clientId is the public Google client ID to pass to GIS.
getOAuthNonce() Promise<string> A single-use, server-issued nonce to pass to GIS when requesting the ID token.
loginWithGoogleCredential(idToken) Promise<user> Completes the login from a GIS ID token.
// Google sign-in (server-verified). Only offer it when configured.
window.ezAuth.getOAuthConfig().then(function (cfg) {
  if (cfg.google && cfg.google.enabled) {
    // Use cfg.google.clientId with Google Identity Services, request a nonce
    // via window.ezAuth.getOAuthNonce(), then complete the login:
    //   window.ezAuth.loginWithGoogleCredential(googleIdToken);
  }
});

Password reset

Method Returns Description
forgotPassword(email) Promise<void> Emails a reset link. Resolves the same way whether or not the account exists.
getResetToken() string or null The reset token from the current page URL (?ezauth_reset_token=...), if present.
resetPassword(token, newPassword) Promise<void> Sets a new password using the token from the emailed link.

The emailed link points back to your site with an ezauth_reset_token query parameter. The drop-in form detects it and shows the "choose a new password" screen automatically; in a custom UI, check getResetToken() on page load. A successful reset revokes the account's existing sessions and does not sign the visitor in — they log in with the new password. Reset links expire after 1 hour.

Email verification

Method Returns Description
verifyEmail(token) Promise<boolean> Completes verification using the token from the emailed link. Resolves true once the email is verified.
resendVerification(email) Promise<void> Re-sends the verification email. Resolves the same way whether or not the account exists.
getVerifyToken() string or null The verification token from the current page URL (?ezauth_verify_token=...), if present.

Registration sends a verification email automatically; the visitor is signed in right away with emailVerified: false until they click the link. The emailed link points back to your site with an ezauth_verify_token query parameter — the drop-in form handles it automatically, or check getVerifyToken() in a custom UI. Verification links expire after 24 hours.

Errors

Every rejected promise carries an EzAuthError with a stable code (and the HTTP status when the failure came from the server). Messages never contain tokens, passwords, or emails.

Code Meaning
not_initialized An action method was called before the SDK was configured.
invalid_request The request was malformed (for example, a missing or invalid email).
unauthorized Wrong credentials, or the session is no longer valid.
account_exists Registration attempted with an email that already has an account.
oauth_not_configured Google sign-in was attempted but is not enabled for the site.
invalid_token The reset or verification token is invalid, expired, or already used.
password_compromised The chosen password appears in known data breaches — ask the visitor to pick another.
rate_limited Too many attempts; try again later.
origin_mismatch The page's origin does not match the configured domain.
upstream_error / internal_error / network_error / invalid_response A transient server or network problem.

Security

  • Passwords are checked against known breach corpuses (Have I Been Pwned); compromised passwords are rejected on registration and reset.
  • Repeated failed logins temporarily lock the account, and registration attempts are rate-limited per IP.
  • Login and password-recovery responses never reveal whether an account exists for an email address.
  • Sessions use short-lived access tokens refreshed automatically by the SDK; requests are only accepted from your configured domain over HTTPS.
  • getUser() exposes only an account ID and verification status — visitor email addresses are available to you through the dashboard CSV export, not through the on-page API.