PHP
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Object-Oriented PHP
-
Section 5: Working with Data
-
Section 6: Modern PHP (PHP 8)
-
Section 7: Working with Files and Networking
-
Section 8: Common PHP Frameworks Overview
-
Section 9: Tooling and Ecosystem
-
Section 10: Practical Projects
-
Section 11: Interview Practice
-
Section 12: More Standard Library
-
Section 13: Data Structures and Algorithms in PHP
-
Section 14: More Practice Exercises
-
Section 15: More Security and Best Practices
-
Section 16: More Testing and Tooling
-
Section 17: WordPress-Style CMS Concepts
-
Section 18: Advanced OOP Practice
-
Section 19: More Web Fundamentals
-
Section 20: Database Practice
-
Section 21: PHP Manual: Array Functions
-
Section 22: PHP Manual: Date and Calendar Functions
-
Section 23: PHP Manual: Filesystem and Directory Functions
-
Section 24: PHP Manual: Filter and Var Handling
-
Section 25: PHP Manual: Math Functions
-
Section 26: PHP Manual: JSON and XML
-
Section 27: PHP Manual: Network and Stream Functions
-
Section 28: PHP Manual: Error and Exception Handling
-
Section 29: PHP Manual: Output Control and Misc
-
Section 30: PHP Manual: FTP, Zip, and Mail
-
Section 31: Modern PHP Frameworks Deep Dive
-
Section 32: PHP Design Patterns
-
Section 33: More Practice Exercises
-
Section 34: PHP Performance and Deployment
-
Section 35: More Interview Practice
-
Section 36: More PHP Standard Library
-
Section 37: PHP Concurrency and Async
-
Section 38: More Web Development Practice
-
Section 39: PHP Testing Deep Dive
-
Section 40: Composer and Package Development
-
Section 41: PHP Security Deep Dive
-
Section 42: More Practical Projects
-
Section 43: Legacy PHP Maintenance
-
Section 44: More Algorithm Practice
-
Section 45: Final Practice and Review
-
Section 46: PHP for E-Commerce Patterns
-
Section 47: PHP API Design Deep Dive
-
Section 48: PHP Caching Strategies
-
Section 49: PHP Queue and Background Jobs
-
Section 50: PHP Multi-Tenancy Patterns
-
Section 51: PHP Real-Time Features
-
Section 52: PHP CMS and Content Modeling
-
Section 53: PHP Internationalization
-
Section 54: More Framework-Specific Practice
-
Section 55: PHP Legacy Code Refactoring
-
Section 56: More Practice Projects Round 2
-
Section 57: PHP Command-Line Applications
-
Section 58: PHP and Microservices
-
Section 59: More Interview and Review Round 2
178: Building a REST API with JWT Authentication
I've seen a lot of developers hit a wall the first time they implement JWTs in PHP. They've got their library installed, their secret key set, and they're sending the token from the frontend—but the server keeps screaming "Invalid Token" or returning a 401 Unauthorized. Usually, the code looks something like this:
// The problematic code
$headers = apache_request_headers();
$token = $headers['Authorization'] ?? null;
if ($token) {
try {
// This is where it crashes
$decoded = JWT::decode($token, new Key($secret_key, 'HS256'));
echo "Welcome, " . $decoded->user_id;
} catch (Exception $e) {
http_response_code(401);
echo "Invalid token: " . $e->getMessage();
}
}
The "Bearer" String Trap
If you run this, it will fail every single time. Why? Because of the HTTP standard. When a frontend client sends a JWT, they don't just send the raw string; they send it in the format Authorization: Bearer eyJhbGci....
The JWT::decode method is expecting just the encoded string. It doesn't know what "Bearer " is. It sees those first seven characters, realizes they aren't part of a valid Base64Url encoded JWT header, and throws an exception. I spent way too many hours debugging this early in my career because I assumed the library handled the header prefix for me. It doesn't.
Cleaning the Token Header
To fix this, you need to strip that prefix before passing the string to the decoder. The most reliable way is to check if the string starts with "Bearer " and then slice it off. Here is how I usually handle it:
$headers = apache_request_headers();
$authHeader = $headers['Authorization'] ?? '';
// Use a regex or a simple string check to extract the actual token
if (preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
$token = $matches[1];
try {
$decoded = JWT::decode($token, new Key($secret_key, 'HS256'));
// Now $decoded contains your payload
} catch (Exception $e) {
http_response_code(401);
echo json_encode(["error" => "Token is expired or invalid"]);
}
} else {
http_response_code(401);
echo json_encode(["error" => "Authorization header not found or malformed"]);
}
By using preg_match, we ensure that we're only grabbing the non-whitespace characters following the word "Bearer". This makes your API much more robust.
Issuing Tokens with Expiration
When you're building the login endpoint, you can't just put the user ID in the token. If that token is stolen, it's valid forever. You need to include an exp (expiration) claim. The firebase/php-jwt library looks for this specific key to automatically invalidate tokens that have lived too long.
I recommend keeping access tokens short-lived—maybe 15 to 60 minutes. Here is a clean way to structure your payload:
$payload = [
'iss' => 'your-api-domain.com', // Issuer
'aud' => 'your-api-domain.com', // Audience
'iat' => time(), // Issued at
'nbf' => time(), // Not before
'exp' => time() + 3600, // Expires in 1 hour
'data' => [ // Your custom user data
'userId' => $user['id'],
'role' => $user['role']
]
];
$jwt = JWT::encode($payload, $secret_key, 'HS256');
Protecting Your API Routes
You don't want to paste that decoding logic into every single file. Instead, wrap it in a middleware function or a helper class. In a REST API, your "Guard" should happen before any controller logic is executed. If the token is missing or invalid, kill the request immediately with a 401 Unauthorized. Only if the token is valid should you attach the userId to the request object and allow the code to proceed to the database queries.
One final tip: Never put sensitive data like passwords or credit card numbers inside a JWT. Remember that JWTs are encoded, not encrypted. Anyone with the token can paste it into a tool like jwt.io and see exactly what's inside the payload. The signature only prevents them from changing the data; it doesn't hide it.
📋 Practical Task
Build a Protected Secure-Note Storage API
Your task is to create a mini-REST API that allows users to save private notes. You will need to implement the following:
- A
/login.phpendpoint: This should accept a username and password. If valid, return a JWT containing theuser_idand an expiration time of 30 minutes. - A
/notes.phpendpoint: This endpoint must be protected. It should check for theAuthorization: Bearer [token]header. If the token is valid, return a JSON list of notes associated with thatuser_id. If the token is missing, expired, or malformed, return a 401 status code. - Implementation Detail: Use a hardcoded secret key for now, but store it in a separate variable at the top of your scripts to simulate an environment variable.
Testing Hint: Use Postman or Insomnia to send the "Authorization" header. Make sure you include the word "Bearer " before your token string.
There are no comments for now.