Plugins

Phone Number

Add SMS OTP and phone-number sign-in flows.

The phone-number plugin stores a phone number on the user, sends OTP codes through an SMS sender you provide, verifies them, and supports phone-based password reset.

Installation

Add the plugin to your server

Pass phone_number() to your Kernia config and supply a send_sms callback. It receives the destination number and the message text to deliver via your SMS provider (Twilio, Vonage, SNS).

auth.py
import os
from kernia import KerniaOptions
from kernia.auth import init
from kernia.plugins.phone_number import phone_number

async def send_sms(phone: str, message: str) -> None:
    await sms_client.messages.create(to=phone, body=message)

auth = init(KerniaOptions(
    database=adapter,
    secret=os.environ["KERNIA_SECRET"],
    base_url=os.environ["KERNIA_BASE_URL"],
    plugins=[phone_number()],
    advanced={"phone-number": {"send_sms": send_sms}},
))

Add the client plugin

Kernia speaks the Better Auth wire protocol, so the official JavaScript client works unchanged. Add the phoneNumber client plugin:

auth-client.ts
import { createAuthClient } from "better-auth/client";
import { phoneNumberClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  baseURL: "/api/auth",
  plugins: [phoneNumberClient()],
});

Usage

Verify a phone number with an OTP

Send a code, then verify it. On a successful verify the number is marked phoneNumberVerified and a session is issued.

await authClient.phoneNumber.sendOtp({
  phoneNumber: "+15551234567",
});

await authClient.phoneNumber.verify({
  phoneNumber: "+15551234567",
  code: "123456",
});

Sign in with phone number and password

For accounts that already have a password, sign in directly with the phone number.

await authClient.signIn.phoneNumber({
  phoneNumber: "+15551234567",
  password: "the-password",
  rememberMe: true,
});

Reset a password over SMS

await authClient.phoneNumber.requestPasswordReset({
  phoneNumber: "+15551234567",
});

await authClient.phoneNumber.resetPassword({
  phoneNumber: "+15551234567",
  otp: "123456",
  newPassword: "new-password",
});

Options

Configure under advanced["phone-number"]:

OptionTypeDefaultDescription
send_smsasync (phone, message) -> NoneRequired. Sends the SMS containing the OTP.
otp_lengthint6Number of digits in the generated code.
expires_inint300Code lifetime in seconds.
disable_sign_upboolFalseReject OTP sign-ins for numbers with no existing account.

Schema

The plugin extends the core user table:

phoneNumber
stringrequired
The user's phone number (unique, nullable).
phoneNumberVerified
booleanrequired
Whether the number has been verified via OTP. Defaults to false.

OTP codes themselves live on the core verification table — no extra migration beyond these two user columns.

Numbers are stored as supplied, so normalize to a canonical format (e.g. E.164) before passing them to the client to keep the phoneNumber uniqueness constraint meaningful.