# Visitor Accounts

Source: https://docs.ezoic.com/docs/identity/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](/docs/subscriptions/visitor-authentication/). 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):

```html
<!-- 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:

```html
<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:

```html
<!-- 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:

```css
.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:

```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](https://developers.google.com/identity/gsi/web) (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. |

```javascript
// 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](https://haveibeenpwned.com/Passwords)); 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](/docs/subscriptions/visitor-authentication/) — how Ezoic Subscriptions uses visitor accounts for checkout and content access.
- [Google One Tap](/docs/identity/google-one-tap/) — One Tap prompt for identity-based ad monetization (separate from Google sign-in for visitor accounts).

