Banata

Configure Authentication

Anonymous Authentication

Let users interact with your app without an account, then upgrade to a full account while preserving their data.

Anonymous authentication lets users start using your app immediately -- no email, no password, no sign-up form. Banata Auth creates a temporary user record and session behind the scenes, so your users can explore features, save preferences, and create content right away. When they are ready, they upgrade to a full account and everything they did as a guest carries over seamlessly.


Use Cases

Anonymous auth shines wherever you want to reduce friction and let people experience your product before committing:

  • Try-before-signup -- Let users explore features, create content, or configure settings before asking them to register.
  • Guest checkout -- Accept purchases or form submissions without requiring an account upfront.
  • Onboarding flows -- Capture preferences or progress during onboarding, then prompt for account creation at a natural breakpoint.
  • Collaborative tools -- Let guests join a shared session or document via a link, then optionally claim their identity later.
  • Mobile-first experiences -- Reduce drop-off by deferring account creation until the user sees value.

Enable Anonymous Auth

Via the Dashboard

  1. Go to Authentication > Methods in your project.
  2. Toggle on Anonymous.
  3. Make sure at least one upgrade method is also enabled (Email & Password, Social OAuth, Magic Links, etc.) so anonymous users have a path to a full account.

Via the SDK

You can also enable anonymous auth programmatically:

typescript
await banata.configuration.saveDashboardConfig({
  authMethods: {
    anonymous: true,
    emailPassword: true, // at least one upgrade method
  },
});

Note: Anonymous auth is most useful when combined with at least one other authentication method that users can upgrade to. Without an upgrade path, anonymous users have no way to secure their account.


Client-Side API

Create an Anonymous Session

Call authClient.signIn.anonymous() to create a guest session. This generates a user record with no credentials and returns a fully functional session:

typescript
import { authClient } from "@/lib/auth-client";
 
const { data, error } = await authClient.signIn.anonymous();
 
if (error) {
  console.error(error.message);
} else {
  console.log(data.user.id);          // "usr_01HXYZ..."
  console.log(data.user.isAnonymous); // true
  console.log(data.session);          // { id, token, expiresAt, ... }
}

Check if the Current User Is Anonymous

Use the isAnonymous flag to decide what UI to show -- for example, displaying an upgrade banner or restricting certain features:

typescript
const session = await authClient.getSession();
 
if (session?.data?.user?.isAnonymous) {
  // Show upgrade prompt
} else {
  // Regular authenticated user
}

Upgrade to Email & Password

Link an email and password to the anonymous user, converting them to a full account:

typescript
const { data, error } = await authClient.anonymous.upgrade({
  email: "user@example.com",
  password: "securePassword123",
  name: "Jane Doe", // optional
});
 
if (error) {
  // Handle errors:
  // - "Email already in use" (409)
  // - "Invalid email" (422)
  // - "Password too short" (422)
  // - "User is not anonymous" (400)
  console.error(error.message);
} else {
  console.log(data.user.isAnonymous); // false
  console.log(data.user.email);       // "user@example.com"
  // The user ID and session remain the same
}

Upgrade via Social OAuth

Anonymous users can also upgrade by linking a social account:

typescript
await authClient.anonymous.upgradeWithSocial({
  provider: "google",
  callbackURL: "/dashboard",
});
// Redirects to Google OAuth flow
// On return, the anonymous user is linked to the Google account

This works with any social provider you have enabled (Google, GitHub, Apple, etc.).


Complete Component Example

Here is a full React component that wraps your app with anonymous session management and an upgrade banner:

tsx
"use client";
 
import { authClient } from "@/lib/auth-client";
import { useState, useEffect } from "react";
 
export function AppShell({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<any>(null);
  const [loading, setLoading] = useState(true);
 
  useEffect(() => {
    initSession();
  }, []);
 
  async function initSession() {
    const session = await authClient.getSession();
 
    if (session?.data?.user) {
      // User already has a session (anonymous or full)
      setUser(session.data.user);
    } else {
      // No session -- create an anonymous one
      const { data } = await authClient.signIn.anonymous();
      if (data) setUser(data.user);
    }
 
    setLoading(false);
  }
 
  if (loading) return <div>Loading...</div>;
 
  return (
    <div>
      {user?.isAnonymous && <UpgradeBanner />}
      {children}
    </div>
  );
}
 
function UpgradeBanner() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");
  const [showForm, setShowForm] = useState(false);
 
  async function handleUpgrade(e: React.FormEvent) {
    e.preventDefault();
    setError("");
 
    const { data, error } = await authClient.anonymous.upgrade({
      email,
      password,
    });
 
    if (error) {
      setError(error.message ?? "Upgrade failed");
    } else {
      // Refresh to reflect the upgraded account
      window.location.reload();
    }
  }
 
  async function handleSocialUpgrade(provider: string) {
    await authClient.anonymous.upgradeWithSocial({
      provider,
      callbackURL: window.location.pathname,
    });
  }
 
  if (!showForm) {
    return (
      <div style={{
        padding: "12px 16px",
        backgroundColor: "#f0f4ff",
        borderBottom: "1px solid #d0d8f0",
        display: "flex",
        alignItems: "center",
        justifyContent: "space-between",
      }}>
        <span>
          You are using a guest account. Your data will be preserved
          when you create an account.
        </span>
        <button onClick={() => setShowForm(true)}>
          Create Account
        </button>
      </div>
    );
  }
 
  return (
    <div style={{
      padding: "16px",
      backgroundColor: "#f0f4ff",
      borderBottom: "1px solid #d0d8f0",
    }}>
      <form onSubmit={handleUpgrade}>
        <h3>Create your account</h3>
        <input
          type="email"
          placeholder="Email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
        />
        <input
          type="password"
          placeholder="Password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          required
        />
        {error && <p style={{ color: "red" }}>{error}</p>}
        <button type="submit">Save Account</button>
        <button type="button" onClick={() => setShowForm(false)}>
          Later
        </button>
      </form>
      <div style={{ marginTop: "12px" }}>
        <p>Or upgrade with a social account:</p>
        <button onClick={() => handleSocialUpgrade("google")}>
          Continue with Google
        </button>
        <button onClick={() => handleSocialUpgrade("github")}>
          Continue with GitHub
        </button>
      </div>
    </div>
  );
}

Tip: Always check for an existing session before calling signIn.anonymous(). If you create a new anonymous session on every page load, you will generate orphaned user records and hit rate limits.


What's Preserved on Upgrade

When an anonymous user upgrades to a full account, the upgrade is an in-place mutation of the existing user record -- not a creation of a new one. This means all foreign key relationships to the user ID stay intact.

DataPreserved?Notes
User IDYesThe id remains exactly the same
SessionYesThe current session continues; no re-authentication needed
Associated recordsYesAny data linked to the user ID (posts, preferences, cart items, etc.)
Organization membershipsYesIf the anonymous user was added to an organization
MetadataYesAny custom metadata set on the user record

What Changes on Upgrade

PropertyBefore UpgradeAfter Upgrade
isAnonymoustruefalse
emailnullThe provided email
namenullThe provided name (if given)
emailVerifiedfalsefalse (a verification email is sent)

Cleanup of Stale Anonymous Accounts

Anonymous users who never upgrade will accumulate over time. You should plan for cleanup from the start.

Automatic Cleanup

You can configure a maximum age for anonymous accounts in your dashboard under Authentication > Methods > Anonymous > Settings, or via the SDK:

typescript
await banata.configuration.saveDashboardConfig({
  authMethods: {
    anonymous: {
      enabled: true,
      maxAge: 30 * 24 * 60 * 60, // 30 days in seconds
    },
  },
});

When maxAge is set, anonymous users whose last session activity exceeds the threshold are automatically cleaned up. The process:

  1. Identifies anonymous users whose last session activity exceeds maxAge.
  2. Deletes the anonymous user record and associated session data.
  3. Logs a user.deleted audit event for each removal.

Manual Cleanup via Admin SDK

If you prefer more control, you can clean up stale anonymous accounts programmatically:

typescript
import { BanataAuth } from "@banata-auth/sdk";
 
const banata = new BanataAuth({
  apiKey: "your-api-key",
  baseUrl: "https://auth.banata.dev",
});
 
// List anonymous users
const { data } = await banata.users.listUsers({
  filter: { isAnonymous: true },
  limit: 100,
});
 
// Delete stale anonymous users
for (const user of data) {
  const age = Date.now() - new Date(user.createdAt).getTime();
  const thirtyDays = 30 * 24 * 60 * 60 * 1000;
 
  if (age > thirtyDays) {
    await banata.users.deleteUser({ userId: user.id });
  }
}

Warning: Deleting an anonymous user removes all associated data. If the anonymous user has created content or records in your app that should be preserved, consider reassigning ownership before deletion.


Rate Limits

Anonymous session creation is rate-limited to prevent abuse:

OperationLimit
Create anonymous session30 requests per minute per IP

If your application exceeds this limit, the API returns a 429 Too Many Requests error. Make sure your frontend checks for an existing session before creating a new anonymous one.


Security Considerations

  1. Rate limits are your first line of defense. Without them, anonymous auth can be abused to create unlimited user records. The built-in limit of 30 per minute per IP keeps this in check.
  2. Limit what anonymous users can do. Use the isAnonymous flag to gate sensitive operations like payments, data exports, and administrative actions. Treat anonymous users as untrusted until they upgrade.
  3. Always configure cleanup. Set a maxAge or implement manual cleanup to prevent unbounded growth of anonymous records in your database.
  4. Session expiry still applies. Anonymous sessions expire according to your standard session lifetime (7 days by default). After expiry, the anonymous user cannot resume their session, and their data may become inaccessible if they have no credentials to re-authenticate.
  5. Prompt upgrades at natural breakpoints. Display clear upgrade prompts after the user completes a meaningful action (finishing a task, before checkout, after saving a document) to maximize conversion from anonymous to full accounts.

Troubleshooting

"User is not anonymous"

You called the upgrade endpoint on a user whose isAnonymous flag is already false. Only anonymous users can be upgraded. If the user already has credentials, they are a full account.

"Email already in use"

The email provided during upgrade belongs to another existing user. An anonymous user cannot claim an email that is already registered. You may want to prompt the user to sign in to the existing account instead.

"Anonymous session expired"

The session expired before the user upgraded. The anonymous user record may still exist, but since there are no credentials to re-authenticate, the user will get a new anonymous session if they return. Data from the previous session may be inaccessible unless you have a recovery mechanism.

"Too many anonymous accounts"

If you see a large number of anonymous user records, check for:

  • Bot traffic creating sessions programmatically.
  • Missing cleanup configuration -- make sure maxAge is set.
  • Frontend bugs that create a new anonymous session on every page load instead of reusing the existing session.

Next Steps