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.
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>
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 (defaulttrue)
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.
Related
- Subscriptions: Visitor Authentication — how Ezoic Subscriptions uses visitor accounts for checkout and content access.
- Google One Tap — One Tap prompt for identity-based ad monetization (separate from Google sign-in for visitor accounts).