HelloJohn / docs
Authentication

Email + Password

Configure email and password authentication in HelloJohn — registration flow, password policy, login, password reset, and email verification.

Email + password is the default authentication method. It's enabled out of the box with no configuration required.

Sign up

import { useAuth } from '@hellojohn/react'

function SignUpForm() {
  const { signUp } = useAuth()

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault()
    const form = new FormData(e.currentTarget)

    await signUp({
      email: form.get('email') as string,
      password: form.get('password') as string,
      name: form.get('name') as string,
    })
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" type="text" placeholder="Full name" required />
      <input name="email" type="email" placeholder="Email" required />
      <input name="password" type="password" placeholder="Password" required />
      <button type="submit">Create account</button>
    </form>
  )
}
import { useAuth } from '@hellojohn/vue'

const { signUp } = useAuth()

async function handleSignUp(email: string, password: string, name: string) {
  await signUp({ email, password, name })
}
import { hellojohn } from './hellojohn'

await hellojohn.auth.signUp({
  email: 'user@example.com',
  password: 'SecurePassword123',
  name: 'Alice Smith',
})
curl -X POST https://your-instance.com/v1/auth/sign-up \
  -H "Content-Type: application/json" \
  -H "X-Client-Id: pk_live_XXXXXXXXXXXXXXXX" \
  -d '{
    "email": "user@example.com",
    "password": "SecurePassword123",
    "name": "Alice Smith"
  }'

Successful response:

{
  "user": {
    "id": "usr_01HABCDEF123456",
    "email": "user@example.com",
    "name": "Alice Smith",
    "email_verified": false,
    "created_at": "2026-01-15T10:00:00Z"
  },
  "session": {
    "access_token": "eyJhbGci...",
    "refresh_token": "rt_01HABCDEF...",
    "expires_at": "2026-01-15T10:15:00Z"
  }
}

Sign in

import { useAuth } from '@hellojohn/react'

function SignInForm() {
  const { signIn } = useAuth()

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault()
    const form = new FormData(e.currentTarget)

    await signIn({
      email: form.get('email') as string,
      password: form.get('password') as string,
    })
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" placeholder="Email" required />
      <input name="password" type="password" placeholder="Password" required />
      <button type="submit">Sign in</button>
    </form>
  )
}
curl -X POST https://your-instance.com/v1/auth/sign-in \
  -H "Content-Type: application/json" \
  -H "X-Client-Id: pk_live_XXXXXXXXXXXXXXXX" \
  -d '{
    "email": "user@example.com",
    "password": "SecurePassword123"
  }'

Password policy

Configure minimum password requirements per tenant:

.env
# Minimum password length (default: 8)
HELLOJOHN_PASSWORD_MIN_LENGTH=12

# Require at least one uppercase letter (default: false)
HELLOJOHN_PASSWORD_REQUIRE_UPPERCASE=true

# Require at least one number (default: false)
HELLOJOHN_PASSWORD_REQUIRE_NUMBER=true

# Require at least one special character (default: false)
HELLOJOHN_PASSWORD_REQUIRE_SPECIAL=true

# Check against HaveIBeenPwned database (default: false)
HELLOJOHN_PASSWORD_CHECK_HIBP=true

Or via the API:

curl -X PATCH https://your-instance.com/cp/v1/tenants/tnt_01HABCDEF \
  -H "Authorization: Bearer {cp_admin_token}" \
  -d '{
    "password_policy": {
      "min_length": 12,
      "require_uppercase": true,
      "require_number": true,
      "check_hibp": true
    }
  }'

Password validation error:

{
  "error": "weak_password",
  "message": "Password must be at least 12 characters and contain an uppercase letter.",
  "violations": ["min_length", "require_uppercase"]
}

Password reset

curl -X POST https://your-instance.com/v1/auth/forgot-password \
  -H "X-Client-Id: pk_live_XXXXXXXXXXXXXXXX" \
  -d '{"email": "user@example.com"}'

HelloJohn sends an email with a reset link. The link expires in 1 hour.

The reset link contains a token: https://yourapp.com/reset-password?token=rt_...

curl -X POST https://your-instance.com/v1/auth/reset-password \
  -H "X-Client-Id: pk_live_XXXXXXXXXXXXXXXX" \
  -d '{
    "token": "rt_01HABCDEF...",
    "new_password": "NewSecurePassword456"
  }'

On success, all existing sessions are revoked and a new session is issued.

Password reset requires SMTP configuration. See SMTP Setup →.

Email verification

By default, users can sign in immediately after registration without verifying their email. To require verification:

.env
# Require email verification before sign-in (default: false)
HELLOJOHN_EMAIL_VERIFICATION_REQUIRED=true

When enabled, sign-in attempts by unverified users return:

{
  "error": "email_not_verified",
  "message": "Please verify your email address before signing in."
}

Resend verification email:

curl -X POST https://your-instance.com/v1/auth/resend-verification \
  -H "X-Client-Id: pk_live_XXXXXXXXXXXXXXXX" \
  -d '{"email": "user@example.com"}'

Change password

Authenticated users can change their password:

curl -X POST https://your-instance.com/v1/users/me/change-password \
  -H "Authorization: Bearer {access_token}" \
  -d '{
    "current_password": "OldPassword123",
    "new_password": "NewPassword456"
  }'

Common errors

ErrorCauseSolution
invalid_credentialsWrong email or passwordShow generic error to user (don't leak which field is wrong)
user_not_foundEmail doesn't existReturn same error as invalid credentials to prevent enumeration
email_already_existsRegistering with an existing emailShow "Already have an account? Sign in"
weak_passwordPassword doesn't meet policyShow specific violations from the violations field
email_not_verifiedEmail verification required and not doneShow resend verification link
rate_limit_exceededToo many sign-in attemptsShow retry timer using retry_after from response

Next steps

On this page