BuddyNext - community engine for WordPress

REST: Auth and Account

Free & Pro

The authentication and account-management routes under buddynext/v1. They cover front-end login, registration, two-factor verification, email verification, member approval, and the self-service account flows (change password, change email, sign out everywhere, manage 2FA, mint/revoke application passwords, and unlink social providers). This page is for developers building a custom login UI, a mobile client, or any client that drives these flows over REST.

The front-end login UI driven by the auth and account REST routes documented on this page

Contract#

These routes follow the same envelope, error shape, and nonce rules as the rest of the API - see the REST contract page (14-rest-contract) for the cross-surface conventions. Two things are specific to this surface:

  • Permission model is unusual here. Most BuddyNext routes gate on a capability or login state. The auth routes intentionally do not: the pre-login routes are public (permission_callback => __return_true) because the caller has no session yet. The account routes and the post-login auth routes gate on "logged in" only. Per-route detail is in the tables below.
  • Authenticated calls still need the cookie + wp_rest nonce. "Public" means no capability check, not "no authentication needed for state to be correct". A logged-in client calls these with the standard X-WP-Nonce header. If the nonce goes stale, mint a fresh one from GET /auth/nonce (see Notes).

Source: includes/Auth/AuthController.php (auth + app-password routes), includes/Auth/TwoFactorController.php (account/2fa routes), and includes/Auth/SocialLogin.php (the social-unlink route).

Auth routes#

All paths below are prefixed with /wp-json/buddynext/v1.

Method Path Auth Purpose
POST /auth/login Public Log a user in by email/username + password. Returns a 2FA challenge token instead of a session when 2FA is enabled.
POST /auth/2fa Public Complete a 2FA challenge with twofa_token + code, finishing the login.
POST /auth/2fa/email-code Public Send a one-time 2FA code by email for the pending challenge (twofa_token).
GET /auth/register/config Public The signup contract plus a fresh guard bundle. Call this before /auth/register. See below.
POST /auth/register Public Create a new user account (email, password, optional user_login, name, terms_agreed, invite, and the guard fields from /auth/register/config: reg_token, challenge_token, challenge_answer).
POST /auth/register/complete Public Finish a parked social sign-up: supply pending_token plus the things OAuth cannot (terms_agreed, required profile fields), then the account is created.
POST /auth/lost-password Public Start a password reset for user_login (email or username).
POST /auth/reset-password Public Complete a reset with key + login + new password.
POST /auth/approve/{id} Admin Approve a pending member (manual-approval registration mode).
POST /auth/verify Public Complete email verification with a token from the verification email/link.
POST /auth/verify/resend Logged in Resend the email-verification message for the current user.
GET /auth/verify/status Logged in Return the current user's email-verification status.
POST /auth/verify/self manage_options Admin-only bypass: mark the current (admin) session verified without an email round-trip, for local/staging setup.
POST /auth/change-password Logged in Set a new password (new_password) after verifying current_password. current_password is not required for a member who signed up through a social provider and has never had a known password (that call sets the first one). Returns 422 with field-keyed errors on failure.
POST /auth/change-email Logged in Change the current user's email (email).
POST /auth/sign-out-everywhere Logged in Destroy all of the current user's sessions on every device.
POST /auth/app-password Logged in Mint a WordPress Application Password for the current user (optional name). The plaintext password is returned once (201).
GET /auth/app-password Logged in List the current user's Application Passwords (metadata only, never the secret).
DELETE /auth/app-password/{uuid} Logged in Revoke one of the current user's Application Passwords.
POST /auth/app-connect Logged in The connect-app bridge's mint step: the browser-side approve screen ({auth}/connect-app/) POSTs here over the member's cookie session and gets back an Application Password credential plus the deep link that returns it to the native app. Body: scheme, bridge_token (required); app_name, app_id, state (optional).
GET /auth/nonce Public Mint a fresh wp_rest nonce for the current session (stale-nonce recovery).

The login, register, 2FA, lost-password, reset-password, and verify routes register with permission_callback => __return_true. They are reachable by anyone, by design, because the caller is pre-session. /auth/approve/{id} checks an admin capability in its own callback; /auth/verify/self requires manage_options; the remaining verify/change/sign-out/app-password/app-connect routes check require_auth (logged in).

GET /auth/register/config - read this before you register#

A non-browser client (a native app, a headless front end) cannot register without calling this first. The anti-bot guard bundle - the time-trap token, the human-check question, the honeypot field name - is otherwise only minted inside the signup template. A client that posts straight to /auth/register without those values is scored as a bot and rejected.

The route is public and returns the signup contract plus a freshly minted guard bundle:

Key Type Meaning
mode string The site's registration mode.
terms bool Whether terms consent is required.
terms_url string Where the terms document lives.
fields array The required profile fields, each { key, label, type, required, options, description }.
reg_token string The time-trap token. Post it back on /auth/register.
honeypot_field string The name of the honeypot input. Send it empty.
challenge array { question, token } when the human check is enabled; an empty array when it is not.

Mint the bundle fresh per signup attempt - the tokens are single-use.

POST /auth/register/complete - finish a parked social sign-up#

An OAuth provider can hand back an identity but not terms consent, and not the site's required profile fields. When that happens the sign-up is parked rather than failed, and the client is given a pending_token. Post it back here with terms_agreed and the outstanding fields, and the account is created.

Account (2FA) routes#

Two-factor enrollment and management for the signed-in user. Every route here requires login (require_auth); there is no public or capability variant.

Method Path Auth Purpose
GET /account/2fa Logged in Return the current user's 2FA status (enabled, method, backup-codes remaining).
POST /account/2fa/setup Logged in Begin TOTP enrollment; returns the secret/QR provisioning data to confirm against.
POST /account/2fa/confirm Logged in Confirm enrollment with a code from the authenticator app, activating 2FA.
POST /account/2fa/disable Logged in Disable 2FA after re-verifying the account password.
POST /account/2fa/backup Logged in Regenerate backup codes after re-verifying the account password.

Social account linking#

Social login itself runs over browser rewrite routes (/oauth/{provider}/ and /oauth/{provider}/callback/), not JSON REST. The one REST route on this surface unlinks a provider from the signed-in user.

Method Path Auth Purpose
DELETE /me/social/{provider} Logged in Unlink a linked social provider (e.g. google, facebook, github, discord) from the current user. Returns {"unlinked": true}.

Examples#

Log in#

A successful login with 2FA off returns the session result. With 2FA on, the response carries a twofa_token and the client must follow up with POST /auth/2fa.

curl -X POST https://example.com/wp-json/buddynext/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{
    "user": "ada@example.com",
    "password": "correct horse battery staple",
    "remember": true
  }'

Parameters: user (required, email or username), password (required), remember (optional boolean, default false). When the account has 2FA enabled, complete the challenge:

curl -X POST https://example.com/wp-json/buddynext/v1/auth/2fa \
  -H 'Content-Type: application/json' \
  -d '{ "twofa_token": "<token-from-login>", "code": "123456" }'

Register#

curl -X POST https://example.com/wp-json/buddynext/v1/auth/register \
  -H 'Content-Type: application/json' \
  -d '{
    "email": "grace@example.com",
    "user_login": "grace",
    "password": "a strong passphrase",
    "terms_agreed": true,
    "invite": "ABCD-1234"
  }'

Required: email, password. Optional: user_login, name, reg_token, challenge_token, challenge_answer (the guard bundle from GET /auth/register/config), terms_agreed (boolean, default false), invite (string, used when the community runs invite-gated or referral registration). Registration honours the site's users_can_register setting and the configured approval mode - when manual approval is on, the new account stays pending until an admin calls POST /auth/approve/{id}.

Change password (validation envelope)#

State-changing account calls send the cookie + nonce and return field-keyed 422 errors on validation failure:

curl -X POST https://example.com/wp-json/buddynext/v1/auth/change-password \
  -H 'Content-Type: application/json' \
  -H 'X-WP-Nonce: <wp_rest nonce>' \
  --cookie 'wordpress_logged_in_...=...' \
  -d '{ "current_password": "old-pass", "new_password": "new-pass-8+chars" }'
{
  "code": "rest_invalid_param",
  "message": "Current password does not match.",
  "data": {
    "status": 422,
    "fields": { "current_password": "Current password does not match." }
  }
}

Notes#

  • Stale-nonce recovery. GET /auth/nonce mints a fresh wp_rest nonce for the current session and sends Cache-Control: no-store. The shared front-end REST client uses it to recover from a 403 on a stale nonce without a full page reload. It re-validates the auth cookie directly, so it never returns an anonymous nonce to a logged-in caller, and the minted nonce is usable only by the same session.
  • 2FA login is a two-step flow. POST /auth/login does not always return a session - if the account has 2FA on, it returns a twofa_token. The client then calls POST /auth/2fa (TOTP/backup code) or requests an email code first via POST /auth/2fa/email-code. Build clients to expect either outcome.
  • Re-authentication on sensitive 2FA changes. Disabling 2FA and regenerating backup codes both require the account password in the request body, not just an active session.
  • Free vs Pro. All routes on this page are part of Free (buddynext/v1). Pro adds its own account-scoped routes under buddynext-pro/v1 (billing, subscriptions, push preferences) - see REST: Pro namespace (24-rest-pro).