Skip to content

User Login

User login is the process of verifying a userโ€™s identity and granting access to the system. The server receives the userโ€™s credentials, looks up the account in the database, verifies the password against the stored hash, and creates a session if the credentials are valid.

The key steps are:

  1. User submits credentials (email/username + password).
  2. Server looks up the user in the database.
  3. Server verifies the password against the stored hash.
  4. If valid, the server creates a session.
  5. User is redirected to a protected page.

A practical approach is to use a single โ€œidentifierโ€ field that accepts either an email address or a username, combined with a password field. This gives users the flexibility to log in with whichever they prefer, without requiring two separate forms.


When verifying passwords, always use password_verify(). Never compare hashes directly.

The wrong approaches:

  • Comparing plain text to a hash ($password === $hash) will never work because they are completely different strings.
  • Hashing the input and comparing (password_hash($input) === $hash) will also fail because each call to password_hash() produces a unique hash due to the random salt.

The correct approach is to use password_verify($plainPassword, $storedHash). This function extracts the salt from the stored hash, applies it to the input password, and performs a constant-time comparison that prevents timing attacks.


The authentication logic follows this pattern:

  1. Try to find the user by email first, then by username.
  2. If no user is found, return null (invalid credentials).
  3. Use password_verify() to compare the submitted password with the stored hash.
  4. If the password matches, return the user data. Otherwise, return null.

Always return the same generic response for any authentication failure. Do not say โ€œEmail not foundโ€ (which reveals that the email is not registered) or โ€œWrong passwordโ€ (which confirms the email exists). Always respond with โ€œInvalid credentialsโ€ to prevent user enumeration attacks.


After a successful login, store the following data in the session:

  • user_id - the database ID
  • user_email - the email address
  • user_name - the full name for display
  • user_role - for authorization checks (admin/customer)
  • is_authenticated - a boolean flag set to true

Storing this data avoids querying the database on every request and enables quick access to user information for display and authorization. Session data is stored on the server; only the session ID is sent to the browser as a cookie.


โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ User enters โ”‚
โ”‚ email/username โ”‚
โ”‚ + password โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚
โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Validate input โ”‚
โ”‚ (required?) โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”
โ”‚ โ”‚
โ–ผ โ–ผ
Valid Invalid
โ”‚ โ”‚
โ”‚ โ””โ”€โ”€> Show error,
โ”‚ redirect back
โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Find user by โ”‚
โ”‚ email/username โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”
โ”‚ โ”‚
โ–ผ โ–ผ
Found Not Found
โ”‚ โ”‚
โ”‚ โ””โ”€โ”€> "Invalid credentials"
โ–ผ redirect back
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Verify passwordโ”‚
โ”‚ password_verifyโ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”
โ”‚ โ”‚
โ–ผ โ–ผ
Match No Match
โ”‚ โ”‚
โ”‚ โ””โ”€โ”€> "Invalid credentials"
โ”‚ redirect back
โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Create session โ”‚
โ”‚ - user_id โ”‚
โ”‚ - user_role โ”‚
โ”‚ - is_auth=true โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚
โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Redirect based โ”‚
โ”‚ on role: โ”‚
โ”‚ Admin > /admin โ”‚
โ”‚ User > /dash โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Middleware is code that runs before your route handler, acting as a gatekeeper. It intercepts every request and decides whether to allow it through or redirect the user.

User Request > Middleware > Route Handler > Response
|
Check session
|
Is authenticated?
/ \
Yes No
| |
Continue Redirect to login

An AuthMiddleware checks whether the session variable is_authenticated is set to true. If it is, the request proceeds to the route handler. If not, the user is redirected to the login page with an error message.


Authorization goes one step further than authentication by restricting access based on user roles.

Two levels of middleware provide this:

  1. AuthMiddleware checks if the user is logged in (any role).
  2. AdminAuthMiddleware checks if the user is logged in and has the admin role.

The AdminAuthMiddleware logic works as follows: if the user is not authenticated, redirect to login. If the user is authenticated but is not an admin, redirect to the user dashboard with an โ€œAccess deniedโ€ message. If the user is authenticated and is an admin, grant access.

RouteNot Logged InCustomerAdmin
/loginAllowAllowAllow
/registerAllowAllowAllow
/dashboardRedirect to loginAllowAllow
/admin/dashboardRedirect to loginDeniedAllow
/admin/usersRedirect to loginDeniedAllow

Public routes have no middleware. User routes use AuthMiddleware. Admin routes use AdminAuthMiddleware, which also checks authentication.


Several security practices are built into this login process:

  • Use prepared statements for all database queries to prevent SQL injection.
  • Return consistent โ€œInvalid credentialsโ€ messages so attackers cannot determine whether a specific email or username exists.
  • Configure session cookies with httponly (prevents JavaScript access), secure (HTTPS only), samesite (CSRF protection), and regenerate the session ID periodically.
  • Consider rate limiting login attempts per IP or user to mitigate brute-force attacks.