Users And Accounts

Update User

Update User Information

auth.api.update_user(
  headers: {"cookie" => cookie},
  body: {
    name: "Jane Doe",
    image: "https://example.com/avatar.png"
  }
)

Email cannot be changed with update_user; use change_email.

Change Email

Enable email changes:

auth = BetterAuth.auth(
  secret: ENV.fetch("BETTER_AUTH_SECRET"),
  user: {
    change_email: {
      enabled: true
    }
  },
  email_verification: {
    send_verification_email: ->(data, _request) { AuthMailer.verify_email(data).deliver_later }
  }
)

Request the change:

auth.api.change_email(
  headers: {"cookie" => cookie},
  body: {
    newEmail: "new@example.com",
    callbackURL: "/settings"
  }
)

The new email becomes active after the verification link is used.

Confirming With Current Email

Require the current email to confirm first:

auth = BetterAuth.auth(
  secret: ENV.fetch("BETTER_AUTH_SECRET"),
  user: {
    change_email: {
      enabled: true,
      send_change_email_confirmation: lambda do |data, _request|
        AuthMailer.with(
          user: data.fetch(:user),
          new_email: data.fetch(:new_email),
          url: data.fetch(:url),
          token: data.fetch(:token)
        ).confirm_email_change.deliver_later
      end
    }
  },
  email_verification: {
    send_verification_email: ->(data, _request) { AuthMailer.verify_email(data).deliver_later }
  }
)

The flow is:

  1. change_email sends a confirmation link to the current email.
  2. The current email confirmation sends a verification link to the new email.
  3. The new email verification updates the user email and marks it verified.

Updating Without Verification

Allow unverified users to update email immediately:

user: {
  change_email: {
    enabled: true,
    update_email_without_verification: true
  }
}

If a verification sender is configured, Better Auth sends a normal verification link for the new email after the update.

Client Usage

HTTP clients call:

curl -i http://localhost:3000/api/auth/change-email \
  -H 'content-type: application/json' \
  -H "cookie: $COOKIE" \
  -d '{"newEmail":"new@example.com","callbackURL":"/settings"}'

Change Password

auth.api.change_password(
  headers: {"cookie" => cookie},
  body: {
    currentPassword: "old-password",
    newPassword: "new-password",
    revokeOtherSessions: true
  }
)

Set Password

Use set_password for users who signed in with OAuth and do not have a credential password.

auth.api.set_password(
  headers: {"cookie" => cookie},
  body: {
    newPassword: "password123"
  }
)

Verify Password

auth.api.verify_password(
  headers: {"cookie" => cookie},
  body: {
    password: "password123"
  }
)

Delete User

Enable user deletion:

user: {
  delete_user: {
    enabled: true
  }
}

Delete with password:

auth.api.delete_user(
  headers: {"cookie" => cookie},
  body: {
    password: "password123"
  }
)

Adding Verification Before Deletion

user: {
  delete_user: {
    enabled: true,
    send_delete_account_verification: lambda do |data, _request|
      AuthMailer.with(
        user: data.fetch(:user),
        token: data.fetch(:token)
      ).delete_account.deliver_later
    end
  }
}

Then complete deletion with the token:

auth.api.delete_user(
  headers: {"cookie" => cookie},
  body: {
    token: params[:token]
  }
)

Authentication Requirements

Deletion requires one of:

  • The correct password.
  • A valid delete-account verification token.
  • A fresh session when no password or token is supplied.

Callbacks

user: {
  delete_user: {
    enabled: true,
    before_delete: ->(user, _request) { AuditLog.create!(user_id: user["id"], action: "before_delete") },
    after_delete: ->(user, _request) { AuditLog.create!(user_id: user["id"], action: "after_delete") }
  }
}

Accounts

Accounts represent credentials and linked OAuth providers.

List User Accounts

auth.api.list_accounts(headers: {"cookie" => cookie})

Token Encryption

Encrypt stored OAuth tokens:

account: {
  encrypt_oauth_tokens: true
}

get_access_token returns decrypted access tokens when the current user has access.

Account Linking

Configure account linking rules:

auth = BetterAuth.auth(
  secret: ENV.fetch("BETTER_AUTH_SECRET"),
  account: {
    account_linking: {
      enabled: true,
      trusted_providers: ["google", "github"],
      allow_different_emails: false,
      update_user_info_on_link: true
    }
  }
)

Forced Linking

Trusted providers can link accounts automatically when provider identity is verified and linking rules allow it.

account: {
  account_linking: {
    trusted_providers: ["google"]
  }
}

Manually Linking Accounts

auth.api.link_social(
  headers: {"cookie" => cookie},
  body: {
    provider: "github",
    callbackURL: "/settings/accounts"
  }
)

Native ID-token linking is available for providers with ID token verification.

Account Unlinking

auth.api.unlink_account(
  headers: {"cookie" => cookie},
  body: {
    providerId: "github",
    accountId: "github-user-id"
  }
)

By default, Better Auth prevents unlinking the user's last account. Set account.account_linking.allow_unlinking_all only when your app has another recovery path.