Introduction
In today’s digital landscape, securing user data is paramount. Implementing a robust user authentication system ensures that only authorized individuals can access specific parts of your application. This guide will walk you through creating an advanced user authentication system in PHP using the Slim Framework, Eloquent ORM, and JWT (JSON Web Tokens) for secure authentication. We’ll cover user registration, login, token generation, and protecting routes to ensure only authenticated users can access certain endpoints. Whether you’re building a simple website or a complex web application, mastering authentication is essential for safeguarding your users and data.
About the Author
[SRIJAN ACHARYA] is a seasoned web developer with extensive experience in PHP and modern web technologies. Passionate about creating secure and efficient applications, [Your Name] enjoys sharing knowledge through comprehensive tutorials and guides to help others excel in web development.
Prerequisites
Before diving into the code, ensure you have the following set up:
Familiarity with JWT concepts is beneficial but not mandator
PHP 7.4 or higher installed on your system.
Composer for managing dependencies.
MySQL or any other relational database.
Setting Up the Project
1. Initialize the Project Directory
Start by creating a new directory for your project and navigating into it:
Basic knowledge of PHP, SQL, and RESTful principles.
mkdir php-authentication
cd php-authentication
Bash2. Initialize Composer
Initialize Composer to manage your project dependencies:
composer init
BashFollow the prompts to set up your composer.json
file.
3. Install Necessary Packages
We’ll use Slim Framework for routing, Eloquent ORM for database interactions, and Firebase PHP-JWT for handling JWT tokens:
composer require slim/slim "^4.0"
composer require illuminate/database "^8.0"
composer require slim/psr7
composer require firebase/php-jwt
BashCreating the Database
For this authentication system, we’ll create a simple users
table.
Create a Database
CREATE DATABASE user_auth;
BashCreate a users
Table
USE user_auth;
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
BashBuilding the Authentication System
1. Setting Up the Entry Point
Create an index.php
file at the root of your project:
<?php
require 'vendor/autoload.php';
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
use Illuminate\Database\Capsule\Manager as Capsule;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
// Initialize Slim App
$app = AppFactory::create();
// Set up Eloquent ORM
$capsule = new Capsule;
$capsule->addConnection([
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'user_auth',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
]);
$capsule->setAsGlobal();
$capsule->bootEloquent();
// Secret key for JWT
$secretKey = "your-secret-key";
// Middleware to handle JSON parsing
$app->addBodyParsingMiddleware();
// Define Routes here
$app->run();
BashExplanation:
Line 34: Runs the Slim application to handle incoming requests.
Line 1: Starts the PHP script.
Line 2: Includes Composer’s autoload to manage dependencies.
Lines 4-8: Import necessary classes from Slim, Eloquent, and JWT libraries.
Line 11: Initializes the Slim application.
Lines 14-23: Configures Eloquent ORM with database credentials and boots it.
Line 26: Defines a secret key used for signing JWT tokens. Ensure you use a strong, unique key in production.
Line 29: Adds middleware to parse JSON bodies in incoming requests.
Line 32: Placeholder for defining API routes.
2. Defining the User Model
Create a User.php
file inside a models
directory:
<?php
namespace Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model {
protected $table = 'users';
protected $fillable = ['name', 'email', 'password'];
protected $hidden = ['password'];
}
BashExplanation:
Line 9: Hides the password
attribute when the model is converted to arrays or JSON.
Line 1: Starts the PHP script.
Line 2: Defines the namespace for the model.
Line 4: Imports the Eloquent Model class.
Line 6: Defines the User
class extending Eloquent’s Model.
Line 7: Specifies the table associated with the model.
Line 8: Defines which attributes are mass assignable.
3. Creating API Routes
Back in index.php
, define the API routes for user registration, login, and protected access:
use Models\User;
// User Registration
$app->post('/register', function (Request $request, Response $response, $args) use ($secretKey) {
$data = $request->getParsedBody();
// Validate input
if (!isset($data['name']) || !isset($data['email']) || !isset($data['password'])) {
$response->getBody()->write(json_encode(['message' => 'Invalid input']));
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
}
// Check if user already exists
$user = User::where('email', $data['email'])->first();
if ($user) {
$response->getBody()->write(json_encode(['message' => 'User already exists']));
return $response->withHeader('Content-Type', 'application/json')->withStatus(409);
}
// Hash password
$hashedPassword = password_hash($data['password'], PASSWORD_DEFAULT);
// Create new user
$newUser = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => $hashedPassword
]);
// Generate JWT Token
$token = JWT::encode(['id' => $newUser->id, 'email' => $newUser->email], $secretKey, 'HS256');
// Respond with token
$response->getBody()->write(json_encode(['token' => $token]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(201);
});
// User Login
$app->post('/login', function (Request $request, Response $response, $args) use ($secretKey) {
$data = $request->getParsedBody();
// Validate input
if (!isset($data['email']) || !isset($data['password'])) {
$response->getBody()->write(json_encode(['message' => 'Invalid input']));
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
}
// Find user by email
$user = User::where('email', $data['email'])->first();
if (!$user) {
$response->getBody()->write(json_encode(['message' => 'User not found']));
return $response->withHeader('Content-Type', 'application/json')->withStatus(404);
}
// Verify password
if (!password_verify($data['password'], $user->password)) {
$response->getBody()->write(json_encode(['message' => 'Invalid credentials']));
return $response->withHeader('Content-Type', 'application/json')->withStatus(401);
}
// Generate JWT Token
$token = JWT::encode(['id' => $user->id, 'email' => $user->email], $secretKey, 'HS256');
// Respond with token
$response->getBody()->write(json_encode(['token' => $token]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
});
// Protected Route Example
$app->get('/profile', function (Request $request, Response $response, $args) {
$user = $request->getAttribute('user');
$response->getBody()->write(json_encode(['user' => $user]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
})->add(function (Request $request, Response $response, $next) use ($secretKey) {
$authHeader = $request->getHeader('Authorization');
if (!$authHeader) {
$response->getBody()->write(json_encode(['message' => 'Authorization header missing']));
return $response->withHeader('Content-Type', 'application/json')->withStatus(401);
}
$token = explode(" ", $authHeader[0])[1] ?? '';
if (!$token) {
$response->getBody()->write(json_encode(['message' => 'Token not provided']));
return $response->withHeader('Content-Type', 'application/json')->withStatus(401);
}
try {
$decoded = JWT::decode($token, new Key($secretKey, 'HS256'));
$user = User::find($decoded->id);
if (!$user) {
$response->getBody()->write(json_encode(['message' => 'User not found']));
return $response->withHeader('Content-Type', 'application/json')->withStatus(404);
}
// Add user to request attributes
$request = $request->withAttribute('user', $user);
return $next($request, $response);
} catch (Exception $e) {
$response->getBody()->write(json_encode(['message' => 'Invalid token']));
return $response->withHeader('Content-Type', 'application/json')->withStatus(401);
}
});
BashExplanation:
- Line 36: Imports the
User
model.
Route 1: User Registration
- Line 39: Defines a POST route at
/register
. - Line 40: Retrieves the parsed body of the request.
- Lines 43-46: Validates the input to ensure
name
,email
, andpassword
are provided. - Lines 49-53: Checks if a user with the provided email already exists. If so, returns a 409 Conflict status.
- Line 56: Hashes the user’s password using PHP’s
password_hash
function for security. - Lines 59-63: Creates a new user record in the database with the provided and hashed data.
- Line 66: Generates a JWT token containing the user’s
id
andemail
, signed with the secret key. - Lines 69-70: Responds with the generated token and a 201 Created status.
Route 2: User Login
- Line 73: Defines a POST route at
/login
. - Line 74: Retrieves the parsed body of the request.
- Lines 77-80: Validates the input to ensure
email
andpassword
are provided. - Lines 83-86: Searches for the user by email. If not found, returns a 404 Not Found status.
- Lines 89-92: Verifies the provided password against the hashed password in the database. If invalid, returns a 401 Unauthorized status.
- Line 95: Generates a JWT token for the authenticated user.
- Lines 98-99: Responds with the generated token and a 200 OK status.
Route 3: Protected Route Example
- Line 102: Defines a GET route at
/profile
. - Line 103: Retrieves the authenticated user from the request attributes.
- Line 104: Responds with the user’s data.
- Line 105: Sets the
Content-Type
header and returns the response with a 200 OK status.
Middleware: Token Validation
Lines 119-128: Attempts to decode the JWT token using the secret key. If successful, retrieves the user from the database and adds the user to the request attributes. If decoding fails, responds with a 401 Unauthorized status.
Line 106: Adds middleware to the /profile
route to handle JWT validation.
Line 107: Retrieves the Authorization
header from the incoming request.
Lines 108-111: If the Authorization
header is missing, responds with a 401 Unauthorized status.
Line 113: Extracts the token from the Authorization
header. The expected format is Bearer <token>
.
Lines 114-117: If the token is not provided, responds with a 401 Unauthorized status.
4. Testing the Authentication System
You can use tools like Postman or cURL to test the API endpoints.
Example: User Registration
Request:
POST /register HTTP/1.1
Host: localhost:8000
Content-Type: application/json
{
"name": "John Doe",
"email": "john.doe@example.com",
"password": "SecurePassword123"
}
BashResponse:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
BashExample: User Login
Request:
POST /login HTTP/1.1
Host: localhost:8000
Content-Type: application/json
{
"email": "john.doe@example.com",
"password": "SecurePassword123"
}
BashResponse:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
BashExample: Accessing a Protected Route
Request:
GET /profile HTTP/1.1
Host: localhost:8000
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
BashResponse:
{
"user": {
"id": 1,
"name": "John Doe",
"email": "john.doe@example.com",
"created_at": "2024-04-27T12:34:56"
}
}
BashRunning the Application
Start the PHP built-in server to run your application:
php -S localhost:8000
BashYour authentication system is now accessible at http://localhost:8000
. You can use Postman, cURL, or any other API testing tool to interact with the endpoints.
Conclusion
Creating a secure user authentication system is a foundational aspect of modern web development. By leveraging the Slim Framework for routing, Eloquent ORM for database interactions, and JWT for token-based authentication, you can build a scalable and secure authentication mechanism in PHP. This guide provided a comprehensive walkthrough, from setting up the project to implementing registration, login, and protected routes. With this foundation, you can further enhance your system by adding features like password reset, email verification, role-based access control, and more to meet the evolving needs of your application.
References
- Slim Framework Documentation
- Eloquent ORM Documentation
- Firebase PHP-JWT Documentation
- PHP Official Website
- Composer Dependency Manager
- Password Hashing in PHP
- JSON Web Tokens Introduction