Skip to main content
POST
/
auth
/
register
Register User
curl --request POST \
  --url https://auth.nullpass.xyz/api/auth/register \
  --header 'Content-Type: application/json' \
  --data '
{
  "email": "jsmith@example.com",
  "password": "<string>",
  "username": "<string>"
}
'
{
  "user": {
    "id": "<string>",
    "email": "jsmith@example.com",
    "username": "<string>",
    "displayName": "<string>",
    "avatar": "<string>",
    "twoFactorEnabled": true,
    "createdAt": "2023-11-07T05:31:56Z"
  },
  "token": "<string>"
}

Endpoint

POST /api/auth/register

Overview

Creates a new user account, generates a JWT token, creates an initial session, and logs audit events. The endpoint is protected by Arcjet with rate limiting (2 requests per token bucket).

Request

email
string
required
User email address. Must be unique and valid email format.
password
string
required
User password. Minimum 8 characters. Will be hashed with bcrypt (10 rounds).
displayName
string
Optional display name for the user (1-100 characters).

Response

user
object
Created user object
token
string
JWT token for authentication. Valid for 7 days (configurable via JWT_EXPIRES_IN).

Implementation Details

Process Flow

  1. CORS Check: Validates CORS headers
  2. Arcjet Protection: Rate limiting (2 requests per bucket)
  3. Email Validation: Arcjet email validation
  4. Duplicate Check: Verifies email doesn’t exist
  5. Password Hashing: bcrypt with 10 rounds
  6. User Creation: Creates user in database
  7. IP Encryption: Encrypts IP address using user-specific key
  8. Session Creation: Creates session with JWT token
  9. Audit Logging: Logs USER_REGISTER and SESSION_CREATE events

Code Reference

export async function POST(request: NextRequest) {
  const corsResponse = handleCors(request)
  if (corsResponse) return corsResponse

  const blocked = await protectRoute(request, { requested: 2 })
  if (blocked) return blocked

  try {
    const body = await request.json()
    const validated = registerSchema.parse(body)

    logger.ups('Register attempt:', validated.email)

    const emailValidation = await validateEmailWithArcjet(request, validated.email)
    if (emailValidation) return emailValidation

    const existingUser = await prisma.user.findUnique({
      where: { email: validated.email },
    })

    if (existingUser) {
      logger.warn('Register failed: User already exists', validated.email)
      return errorResponse('User already exists', 409, request.headers.get('origin'))
    }

    const passwordHash = await bcrypt.hash(validated.password, 10)

    const user = await prisma.user.create({
      data: {
        email: validated.email,
        passwordHash,
        displayName: validated.displayName,
      },
      select: {
        id: true,
        email: true,
        displayName: true,
        createdAt: true,
      },
    })

    const clientIp = getClientIp(request)
    const encryptedIp = getClientIpForStorage(request, user.id)

    const token = generateToken({ userId: user.id, email: user.email })
    const expiresAt = getSessionExpiresAt()

    await prisma.session.create({
      data: {
        userId: user.id,
        token,
        expiresAt,
        ip: encryptedIp,
      },
    })

    await createAuditLog(user.id, 'USER_REGISTER', {
      email: user.email,
      ip: encryptedIp,
    })
    await createAuditLog(user.id, 'SESSION_CREATE', {
      ip: encryptedIp,
    })

    return jsonResponse(
      {
        user,
        token,
      },
      201,
      request.headers.get('origin')
    )
  } catch (error: any) {
    if (error.name === 'ZodError') {
      logger.warn('Register validation error:', error.errors)
      return errorResponse(error.errors[0].message, 400, request.headers.get('origin'))
    }
    logger.error('Register error:', error)
    return errorResponse('Internal server error', 500, request.headers.get('origin'))
  }
}

Status Codes

201
Created
User successfully created
400
Bad Request
Validation error (invalid email format, password too short, etc.)
409
Conflict
User with this email already exists
403
Forbidden
Blocked by Arcjet (rate limit exceeded, bot detected, etc.)
500
Internal Server Error
Server error during user creation

Example Request

curl -X POST https://auth.nullpass.xyz/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "password": "securepassword123",
    "displayName": "John Doe"
  }'

Example Response

{
  "user": {
    "id": "clx1234567890abcdef",
    "email": "user@example.com",
    "displayName": "John Doe",
    "createdAt": "2024-01-01T00:00:00.000Z"
  },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Audit Events

This endpoint creates the following audit log entries:
  • USER_REGISTER: User account created
  • SESSION_CREATE: Initial session created

Security Considerations

  • Password is hashed with bcrypt (10 rounds) before storage
  • IP address is encrypted using user-specific encryption key
  • Email validation performed via Arcjet
  • Rate limiting prevents abuse (2 requests per bucket)
  • All actions are logged in audit trail

Body

application/json
email
string<email>
required
password
string
required
Minimum string length: 8
username
string

Response

User created successfully

user
object
token
string