Banata

Configure Authentication

Username Authentication

Sign in with a username and password instead of an email address.

Username authentication lets your users sign up and sign in with a username and password instead of an email address. This is ideal for apps where a handle or gamertag is the primary identity -- gaming platforms, social networks, internal tools, and developer communities.

You can use username auth on its own or combine it with email/password so users have the flexibility to sign in either way.


When to Use Username Auth

ScenarioRecommended?Why
Gaming platformsYesPlayers identify by gamertag or handle
Social appsYesUsernames are the public identity
Internal toolsYesEmployees use a short handle instead of a full email
Developer platformsYesDevelopers expect username-based accounts (like GitHub)
E-commerceNoEmail is needed for order confirmations and receipts
Enterprise SaaSNoEmail is the standard identity in business contexts

Enabling Username Auth

From the Dashboard

  1. Open your project in the Banata Auth dashboard.
  2. Go to Authentication > Methods.
  3. Toggle on Username & Password.
  4. Click Save.

That's it -- your project now accepts username-based sign-ups and sign-ins.

From the SDK

You can also enable username auth programmatically:

ts
await banata.configuration.saveDashboardConfig({
  authMethods: {
    username: true,
  },
});

No email provider is required for username-only auth, since there are no verification emails or password reset links to send.


Username Validation Rules

Banata Auth enforces the following rules on all usernames:

RuleValue
Minimum length3 characters
Maximum length32 characters
Must start withA letter (a-z)
Allowed charactersLowercase letters (a-z), numbers (0-9), underscores (_), hyphens (-)
Case sensitivityCase-insensitive -- JaneDoe and janedoe are treated as the same username
UniquenessGlobally unique -- no two users in your project can share a username

Usernames are normalized to lowercase before storage. If a user signs up as JaneDoe, it is stored and compared as janedoe.


Client API

The auth client exposes three methods for username auth:

  • authClient.signUp.username() -- Create a new account with a username and password.
  • authClient.signIn.username() -- Sign in to an existing account.
  • authClient.username.checkAvailability() -- Check whether a username is already taken.

Sign Up

ts
import { authClient } from "@/lib/auth-client";
 
const { data, error } = await authClient.signUp.username({
  username: "janedoe",
  password: "securePassword123",
  name: "Jane Doe",              // optional display name
  email: "jane@example.com",     // optional -- only relevant if email auth is also enabled
});
 
if (error) {
  console.error(error.message);
} else {
  console.log(data.user);     // { id, username, name, ... }
  console.log(data.session);  // { id, token, expiresAt, ... }
}

Sign In

ts
const { data, error } = await authClient.signIn.username({
  username: "janedoe",
  password: "securePassword123",
});
 
if (error) {
  console.error(error.message);
} else {
  window.location.href = "/dashboard";
}

Check Availability

Before the user submits the sign-up form, you can check if their desired username is available:

ts
const { data } = await authClient.username.checkAvailability({
  username: "janedoe",
});
 
console.log(data.available); // true or false

This is useful for showing real-time feedback as the user types.


Complete Sign-Up Form with Availability Checking

Here is a full React example that debounces the availability check and disables the submit button when the username is taken:

tsx
"use client";
 
import { authClient } from "@/lib/auth-client";
import { useState, useEffect } from "react";
 
export default function UsernameSignUpPage() {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const [name, setName] = useState("");
  const [error, setError] = useState("");
  const [usernameStatus, setUsernameStatus] = useState<
    "idle" | "checking" | "available" | "taken"
  >("idle");
 
  // Debounced availability check
  useEffect(() => {
    if (username.length < 3) {
      setUsernameStatus("idle");
      return;
    }
 
    setUsernameStatus("checking");
    const timer = setTimeout(async () => {
      const { data } = await authClient.username.checkAvailability({
        username,
      });
      setUsernameStatus(data?.available ? "available" : "taken");
    }, 500);
 
    return () => clearTimeout(timer);
  }, [username]);
 
  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setError("");
 
    const { data, error } = await authClient.signUp.username({
      username,
      password,
      name: name || undefined,
    });
 
    if (error) {
      setError(error.message ?? "Sign-up failed");
    } else {
      window.location.href = "/dashboard";
    }
  }
 
  return (
    <form onSubmit={handleSubmit}>
      <h1>Create Account</h1>
 
      <div>
        <label htmlFor="username">Username</label>
        <input
          id="username"
          type="text"
          placeholder="janedoe"
          value={username}
          onChange={(e) => setUsername(e.target.value.toLowerCase())}
          minLength={3}
          maxLength={32}
          pattern="[a-z][a-z0-9_-]*"
          required
        />
        {usernameStatus === "checking" && <span>Checking...</span>}
        {usernameStatus === "available" && (
          <span style={{ color: "green" }}>Available</span>
        )}
        {usernameStatus === "taken" && (
          <span style={{ color: "red" }}>Already taken</span>
        )}
      </div>
 
      <div>
        <label htmlFor="password">Password</label>
        <input
          id="password"
          type="password"
          placeholder="Minimum 8 characters"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          minLength={8}
          required
        />
      </div>
 
      <div>
        <label htmlFor="name">Display Name (optional)</label>
        <input
          id="name"
          type="text"
          placeholder="Jane Doe"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
      </div>
 
      {error && <p style={{ color: "red" }}>{error}</p>}
 
      <button
        type="submit"
        disabled={usernameStatus === "taken" || usernameStatus === "checking"}
      >
        Create Account
      </button>
    </form>
  );
}

Sign-In Form Example

A minimal sign-in form for username auth:

tsx
"use client";
 
import { authClient } from "@/lib/auth-client";
import { useState } from "react";
 
export default function UsernameSignInPage() {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");
 
  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setError("");
 
    const { data, error } = await authClient.signIn.username({
      username,
      password,
    });
 
    if (error) {
      setError(error.message ?? "Sign-in failed");
    } else {
      window.location.href = "/dashboard";
    }
  }
 
  return (
    <form onSubmit={handleSubmit}>
      <h1>Sign In</h1>
 
      <input
        type="text"
        placeholder="Username"
        value={username}
        onChange={(e) => setUsername(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">Sign In</button>
    </form>
  );
}

Combining Username with Email/Password

You can enable both authentication methods so users can sign in with either a username or an email address:

  1. In the dashboard, toggle on both Username & Password and Email & Password under Authentication > Methods.
  2. Or via the SDK:
ts
await banata.configuration.saveDashboardConfig({
  authMethods: {
    username: true,
    emailPassword: true,
  },
});

When both are enabled, users can optionally provide an email during sign-up. If they do, they can sign in with either credential:

ts
// Sign in with username
await authClient.signIn.username({
  username: "janedoe",
  password: "securePassword123",
});
 
// Sign in with email (if the user provided one at sign-up)
await authClient.signIn.email({
  email: "jane@example.com",
  password: "securePassword123",
});

Both methods authenticate the same user and create the same session.


Password Reset Without Email

If a user signed up with only a username and no email, the standard "forgot password" email flow is not available. You have a few options:

  1. Require an email at sign-up. Even in a username-first flow, you can make email mandatory so password reset is always available.
  2. Admin reset. Use the server-side SDK to reset a user's password directly:
ts
await banata.users.updateUser({
  userId: "usr_01HXYZ...",
  password: "newTemporaryPassword",
});
  1. Prompt the user to add an email. If the user's account has no email, prompt them to add one before they can use self-service password reset.

Tip: If your app targets an audience that may forget passwords often (like younger users in gaming), requiring an email at sign-up is strongly recommended even when username is the primary identifier.


Rate Limits

Username auth endpoints are rate-limited to prevent brute-force attacks:

EndpointLimit
Sign In (signIn.username)30 requests per minute
Sign Up (signUp.username)10 requests per minute
Check Availability (username.checkAvailability)60 requests per minute

When a limit is exceeded, the server returns a 429 Too Many Requests response. Your client should display a message like "Too many attempts -- please try again in a moment."


Error Reference

ErrorHTTP StatusWhen It Occurs
AuthenticationError401Invalid username or password on sign-in
ForbiddenError403The account has been banned
ConflictError409The username is already taken on sign-up
ValidationError422Invalid username format, username too short/long, or password too short
RateLimitError429Too many requests to the endpoint

You can handle these by checking the error.status code:

ts
const { error } = await authClient.signUp.username({
  username: "janedoe",
  password: "securePassword123",
});
 
if (error?.status === 409) {
  // Username is taken -- suggest the user pick a different one
}
 
if (error?.status === 422) {
  // Validation failed -- show the error message to the user
}

Troubleshooting

"Invalid username format"

The username does not meet the validation rules. Make sure it:

  • Is between 3 and 32 characters long
  • Starts with a letter (a-z)
  • Contains only lowercase letters, numbers, underscores, and hyphens

"Username already taken"

Another user has already claimed that username. Remember that usernames are case-insensitive, so JaneDoe and janedoe count as the same name. Prompt the user to choose a different one, or use checkAvailability to give feedback before they submit.

"Cannot reset password"

The user signed up with a username only and has no email on their account. Use the admin SDK to reset their password, or ask them to add an email address first.

Sign-in returns 401 but the username exists

The most likely cause is an incorrect password. The error message is intentionally vague ("Invalid username or password") to prevent username enumeration. Double-check that the password is correct and that Caps Lock is not enabled.


Next Steps

  • Email & Password -- Add traditional email-based authentication alongside username auth
  • Social OAuth -- Let users link a Google, GitHub, or other social account
  • Anonymous Auth -- Allow guest access with optional upgrade to a full account
  • Roles & Permissions -- Control what authenticated users can access