KAHIBARO
Discord Login Register

30.1. User Registration

Understanding User Registration

User registration is the process where a new user creates an account in your system. It is the first security-critical step in an authentication system, because you decide:

In this chapter, you focus on what happens specifically during registration, not on later steps like login or email verification which are covered in other chapters.

::danger
Never store raw passwords. Always hash them using a secure algorithm before saving to the database.


Typical Registration Flow

A common registration flow looks like this:

  1. Client sends registration data
    For example, a JSON body:
json
   {
     "email": "alice@example.com",
     "password": "super-secret-password",
     "full_name": "Alice Doe"
   }
  1. Backend validates the input
    • Are required fields present?
    • Is the email valid?
    • Is the password strong enough?
    • Is the email already in use?
  2. Backend hashes the password
    • The raw password is never stored.
    • A secure password hashing function is used (covered in another chapter).
  3. Backend creates a user record in the database
    It stores something like:

| Field | Example value |
|--------------|---------------------------------------|
| id | 1 |
| email | alice@example.com |
| password_hash| $2b$12$... (bcrypt hash) |
| full_name | Alice Doe |
| is_active | true |
| is_verified | false (before email verification) |
| created_at | 2026-08-28T10:00:00Z |

  1. Backend returns a response
    • Usually a 201 Created HTTP status.
    • The response never includes the password or password hash.

Example JSON response:

json
   {
     "id": 1,
     "email": "alice@example.com",
     "full_name": "Alice Doe",
     "is_active": true,
     "is_verified": false
   }

Designing a Registration Request

A backend needs to define what fields are required and how they are sent.

Common registration fields

Typical fields include:

FieldTypeRequiredNotes
emailstringyesMust be unique, valid email format
passwordstringyesWill be hashed before storage
full_namestringoptionalFor display purposes
usernamestringoptionalIf your system uses usernames
accept_termsbooleanyesIf you require agreement to terms of service

You might also support additional optional fields, like language preference or timezone, but be careful not to collect unnecessary personal data.

Example: JSON request body

json
{
  "email": "bob@example.com",
  "password": "MyS3cureP@ssw0rd",
  "full_name": "Bob Smith",
  "accept_terms": true
}

Do not expose internal fields

Fields like id, is_active, is_admin, or is_verified should not be accepted from the client during registration. They are controlled by the server.

For example, this is unsafe and must not be allowed:

json
{
  "email": "bob@example.com",
  "password": "MyS3cureP@ssw0rd",
  "is_admin": true
}

If your backend blindly stores is_admin: true from this input, anyone could register as an administrator.


Input Validation for Registration

Validation protects your system and helps users correct their mistakes.

Types of validation

  1. Syntactic validation
    These checks focus on format.
    • Email format: contains @, has a domain, etc.
    • Password length: for example minimum 8 or 12 characters.
    • Non-empty fields: full_name, username, etc.
  2. Semantic validation
    These checks focus on the meaning.
    • Email is not already registered.
    • Username is not taken.
    • User accepted terms of service.
  3. Security related validation
    • Password is not too weak (for example "password123").
    • Request is not too large.
    • Fields do not contain dangerous content.

Example validation rules

You can summarize some typical rules:

RuleExample
Email requiredReject if email is missing
Email formatRegex check like .+@.+\\..+
Password lengthAt least 8 or 12 characters
Password complexity (optional)At least 1 upper, 1 lower, 1 digit, 1 symbol
Email uniquenessReject if user with same email exists
Terms acceptance (if required)accept_terms must be true

::danger
Do not rely only on frontend validation. Always validate input again on the backend.

Example: Handling a validation error

Suppose a user sends:

json
{
  "email": "not-an-email",
  "password": "123",
  "accept_terms": false
}

The backend might respond:

json
{
  "detail": [
    {"field": "email", "message": "Invalid email format"},
    {"field": "password", "message": "Password must be at least 8 characters"},
    {"field": "accept_terms", "message": "You must accept the terms of service"}
  ]
}

The HTTP status would typically be 422 Unprocessable Entity or 400 Bad Request.


Creating a User Record

Once you validate input and hash the password, you create a user in the database.

Basic user table schema

In SQL style, a simple users table might look like this:

sql
CREATE TABLE users (
    id           SERIAL PRIMARY KEY,
    email        VARCHAR(255) NOT NULL UNIQUE,
    password_hash TEXT NOT NULL,
    full_name    VARCHAR(255),
    is_active    BOOLEAN NOT NULL DEFAULT TRUE,
    is_verified  BOOLEAN NOT NULL DEFAULT FALSE,
    created_at   TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Key ideas:

Handling email uniqueness

Because the email column is unique, if two users try to register with the same email, one will fail.

Scenario:

  1. First request:
    email = "alice@example.com"
    User is created successfully.
  2. Second request:
    email = "alice@example.com" again.

The database will raise a unique constraint error. Your application should catch this and return a friendly error, such as:

json
{
  "detail": [
    {"field": "email", "message": "This email is already registered"}
  ]
}

Example Registration Endpoint Behavior

To make the behavior concrete, here is what a registration endpoint might do step by step, implementation-agnostic:

  1. Receive HTTP POST request
    • Method: POST
    • URL: /auth/register
    • Content-Type: application/json
    • Body: user registration data
  2. Parse JSON body
    • Convert JSON into an internal representation, for example a Python object or a DTO.
  3. Validate fields
    • Check presence and format.
    • Check uniqueness in the database.
  4. Hash password
    • Use a function like hash_password(plain_password) that returns a secure hash string.
  5. Create and save user
    • Insert a new record into the users table with the email, password hash, and other fields.
  6. Return response
    • Status: 201 Created
    • Body: user data without the password or hash.

Example JSON:

json
   {
     "id": 42,
     "email": "newuser@example.com",
     "full_name": "New User",
     "is_active": true,
     "is_verified": false
   }

Example: Error flow

Request:

json
{
  "email": "existing@example.com",
  "password": "AnotherStrongPass123!"
}

If existing@example.com already exists, the backend might respond:

json
  {
    "detail": [
      {"field": "email", "message": "This email is already registered"}
    ]
  }

What to Return After Registration

Different systems choose different post-registration behavior. Some common options:

1. Require login after registration

Flow:

  1. User registers.
  2. You create the account.
  3. You return user data only, no tokens.
  4. User must then call the login endpoint with email and password.

This is simple and keeps registration and login separate.

2. Automatic login after registration

Flow:

  1. User registers.
  2. You create the account.
  3. You issue an access token (and maybe a refresh token).
  4. The response includes token(s) so the user is already authenticated.

Example response:

json
{
  "user": {
    "id": 42,
    "email": "newuser@example.com",
    "full_name": "New User",
    "is_active": true,
    "is_verified": false
  },
  "access_token": "<jwt-token-here>",
  "token_type": "bearer"
}

Automatic login is convenient, but you must carefully consider how it interacts with email verification, which is discussed separately.

3. Require email verification

Typical combination:

  1. User registers.
  2. You create the account with is_verified = false.
  3. You send a verification email in a background job.
  4. You either:
    • Allow limited access until verified, or
    • Disallow login until email is verified.

This chapter focuses on registration itself, while details about email verification and tokens are covered in their own chapters.


Security Considerations in Registration

Even at registration time, some security rules are important.

Avoid leaking information

An attacker might try to discover which emails are registered. For example, if your registration endpoint returns:

Then the attacker can test a list of emails and see which ones are valid accounts. This is called user enumeration.

Possible mitigations:

Rate limiting

Implement rate limiting on registration to prevent attackers from trying millions of email addresses or passwords.

For example, you might allow:

The details of rate limiting implementation are covered in other chapters, but you should design registration with this in mind.

Do not log raw passwords

When you log incoming HTTP requests, make sure you do not log the password field. Either:

::danger
Never store or log raw passwords. Even in development or test environments.


Example Walkthrough

Imagine three requests to your /auth/register endpoint.

Request 1: Successful registration

Request:

json
{
  "email": "carol@example.com",
  "password": "StrongP@ssw0rd!",
  "full_name": "Carol Lee",
  "accept_terms": true
}

Backend steps:

  1. Validate email format: OK.
  2. Validate password length and complexity: OK.
  3. Check accept_terms: true.
  4. Check email is not already registered: OK.
  5. Hash password to something like $2b$12$abc....
  6. Insert new user into database.
  7. Return 201 Created with user info.

Response:

json
{
  "id": 7,
  "email": "carol@example.com",
  "full_name": "Carol Lee",
  "is_active": true,
  "is_verified": false
}

Request 2: Email already used

Request:

json
{
  "email": "carol@example.com",
  "password": "AnotherP@ssw0rd!",
  "full_name": "Someone Else",
  "accept_terms": true
}

Backend steps:

  1. Validate format: OK.
  2. Query database, find carol@example.com already exists.
  3. Return error.

Response:

json
{
  "detail": [
    {"field": "email", "message": "This email is already registered"}
  ]
}

Request 3: Invalid data

Request:

json
{
  "email": "bad-email",
  "password": "123",
  "accept_terms": false
}

Backend steps:

  1. Email format invalid.
  2. Password too short.
  3. Terms not accepted.
  4. Return validation errors.

Response:

json
{
  "detail": [
    {"field": "email", "message": "Invalid email format"},
    {"field": "password", "message": "Password must be at least 8 characters"},
    {"field": "accept_terms", "message": "You must accept the terms of service"}
  ]
}

Summary

During user registration, the backend must:

Once registration works correctly, you are ready to connect it to login, tokens, and email verification, which are handled in other chapters.

Views: 8

Comments

Please login to add a comment.

Don't have an account? Register now!