Lesson 141 of 158 – Project JWT Authentication
89%

Project JWT Authentication

In this lesson, we will add JWT authentication to our Student Management project.

In the previous lesson, the login API verified the user's email and password. Now, after successful authentication, the PHP API will create a JWT token and return it to the React Native application.

Project Goal: Generate, return, store, and use a JWT token after successful user login so that protected Student Management APIs can identify the authenticated user.

1. What is JWT Authentication?

JWT stands for JSON Web Token. It is a compact token commonly used to represent authenticated information between a client and server.

Login
  ↓
Verify Email + Password
  ↓
Create JWT
  ↓
Return JWT
  ↓
React Native
  ↓
Protected API Requests

2. Why Our Project Needs JWT

Our Student Management APIs should not be publicly accessible. The server needs a way to determine whether a request comes from an authenticated user.

Mobile App
    ↓
JWT Token
    ↓
PHP API
    ↓
Verify Token
    ↓
Allow Protected Operation

3. Login and JWT Flow

React Native
      ↓
POST /login.php
      ↓
PHP
      ↓
Find User
      ↓
password_verify()
      ↓
Create JWT
      ↓
JSON Response
      ↓
React Native
      ↓
Store Token

4. JWT Structure

A JWT normally contains three parts separated by dots.

HEADER.PAYLOAD.SIGNATURE
  • Header describes the token.
  • Payload contains claims.
  • Signature allows the server to verify integrity.

5. JWT Header

A JWT header commonly contains the signing algorithm and token type.

{
    "alg": "HS256",
    "typ": "JWT"
}

The header is part of the token and is encoded using Base64URL.

6. JWT Payload

The payload contains claims about the token.

{
    "sub": 1,
    "email": "rahul@example.com",
    "role": "user",
    "iat": 1760000000,
    "exp": 1760003600
}

The payload should not contain sensitive information such as passwords.

7. JWT Signature

The signature is created using the JWT header, payload, and a secret key or another signing mechanism depending on the selected algorithm.

Header
   +
Payload
   +
Secret Key
   ↓
Signature

The server uses the signing information to verify that the token is valid.

8. Installing Firebase PHP JWT

A common PHP library for working with JWT is firebase/php-jwt.

Install it with Composer:

composer require firebase/php-jwt

Composer will create the required vendor files.

9. Loading the JWT Library

require_once __DIR__ .
    '/vendor/autoload.php';

The Composer autoloader makes the JWT library classes available to PHP.

10. Import JWT Classes

use Firebase\JWT\JWT;

We can then use the JWT class to create tokens.

11. Secret Key

The server needs a secret key when using a symmetric algorithm such as HS256.

$secretKey =
    'CHANGE_THIS_TO_A_LONG_RANDOM_SECRET';
Important: Do not use a simple example secret in a production application. Store the real secret securely on the server.

12. Token Claims

Useful claims for our project include:

Claim Purpose
sub User identifier
email User email
role User role
iat Token creation time
exp Token expiration time

13. Creating Token Times

$issuedAt = time();

$expiresAt =
    $issuedAt + (60 * 60);

In this example, the token is configured to expire after one hour.

14. Creating the JWT Payload

$payload = [
    'sub' => $user['id'],
    'email' => $user['email'],
    'role' => $user['role'],
    'iat' => $issuedAt,
    'exp' => $expiresAt
];

The sub claim identifies the authenticated user.

15. Encoding the JWT

$token = JWT::encode(
    $payload,
    $secretKey,
    'HS256'
);

The resulting token can be returned to the React Native application.

16. Login Response with JWT

echo json_encode([
    "success" => true,
    "message" => "Login successful",
    "token" => $token,
    "data" => [
        "id" => $user['id'],
        "name" => $user['name'],
        "email" => $user['email'],
        "role" => $user['role']
    ]
]);

The password and password hash are not included in the response.

17. Complete Login + JWT Flow

Email + Password
       ↓
Find User
       ↓
password_verify()
       ↓
Create Claims
       ↓
JWT::encode()
       ↓
JWT Token
       ↓
JSON Response

18. Complete PHP JWT Login API

<?php

header(
    "Content-Type: application/json"
);

require_once '../config/database.php';
require_once __DIR__ .
    '/vendor/autoload.php';

use Firebase\JWT\JWT;

$secretKey =
    'CHANGE_THIS_TO_A_LONG_RANDOM_SECRET';

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {

    http_response_code(405);

    echo json_encode([
        "success" => false,
        "message" => "Method not allowed"
    ]);

    exit;
}

$input = json_decode(
    file_get_contents("php://input"),
    true
);

$email = trim($input['email'] ?? '');
$password = $input['password'] ?? '';

if ($email === '' || $password === '') {

    http_response_code(422);

    echo json_encode([
        "success" => false,
        "message" =>
            "Email and password are required"
    ]);

    exit;
}

try {

    $stmt = $pdo->prepare(
        "SELECT id, name, email, password, role
         FROM users
         WHERE email = ?"
    );

    $stmt->execute([$email]);

    $user = $stmt->fetch(
        PDO::FETCH_ASSOC
    );

    if (!$user ||
        !password_verify(
            $password,
            $user['password']
        )) {

        http_response_code(401);

        echo json_encode([
            "success" => false,
            "message" =>
                "Invalid email or password"
        ]);

        exit;
    }

    $issuedAt = time();

    $expiresAt =
        $issuedAt + (60 * 60);

    $payload = [
        'sub' => $user['id'],
        'email' => $user['email'],
        'role' => $user['role'],
        'iat' => $issuedAt,
        'exp' => $expiresAt
    ];

    $token = JWT::encode(
        $payload,
        $secretKey,
        'HS256'
    );

    http_response_code(200);

    echo json_encode([
        "success" => true,
        "message" => "Login successful",
        "token" => $token,
        "data" => [
            "id" => $user['id'],
            "name" => $user['name'],
            "email" => $user['email'],
            "role" => $user['role']
        ]
    ]);

} catch (PDOException $e) {

    error_log($e->getMessage());

    http_response_code(500);

    echo json_encode([
        "success" => false,
        "message" => "Server error"
    ]);
}

19. React Native Login Response

React Native will receive the token from the login API.

{
    "success": true,
    "message": "Login successful",
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "data": {
        "id": 1,
        "name": "Rahul Kumar",
        "email": "rahul@example.com",
        "role": "user"
    }
}

The token should then be stored so it can be used for protected requests.

20. TypeScript Login Response

interface LoginResponse {
    success: boolean;
    message: string;
    token: string;
    data: {
        id: number;
        name: string;
        email: string;
        role: string;
    };
}

This interface describes the successful JWT login response.

21. Axios Login Request

const response =
    await api.post<LoginResponse>(
        "/login.php",
        {
            email,
            password
        }
    );

const token =
    response.data.token;

The token can now be stored locally.

22. Store JWT Token

React Native can use AsyncStorage for basic token persistence.

await AsyncStorage.setItem(
    "authToken",
    token
);

For applications with stronger security requirements, platform secure storage should be considered for sensitive credentials.

23. Sending JWT to Protected APIs

The token is normally sent using the Authorization header.

Authorization:
Bearer YOUR_JWT_TOKEN

Example with Axios:

const response =
    await api.get(
        "/students.php",
        {
            headers: {
                Authorization:
                    `Bearer ${token}`
            }
        }
    );

24. PHP Reads Authorization Header

The protected PHP API needs to extract the Bearer token from the Authorization header.

$headers = getallheaders();

$authorization =
    $headers['Authorization']
    ?? '';

if (
    !preg_match(
        '/Bearer\s(\S+)/',
        $authorization,
        $matches
    )
) {

    http_response_code(401);

    echo json_encode([
        "success" => false,
        "message" =>
            "Authentication required"
    ]);

    exit;
}

$token = $matches[1];

25. JWT Verification Concept

The server must verify the token before allowing access to a protected endpoint.

Request
   ↓
Read Authorization Header
   ↓
Extract JWT
   ↓
Verify Signature
   ↓
Check Expiration
   ↓
Read User ID
   ↓
Allow Request

The actual verification code will be implemented in the upcoming protected API lessons.

26. Token Expiration

Our JWT contains an exp claim.

'exp' => $expiresAt

Once the token expires, protected API requests should no longer accept it.

Expired JWT
    ↓
HTTP 401
    ↓
React Native
    ↓
Login Again

27. Testing JWT Login with Postman

Send a POST request to:

https://example.com/api/login.php

Body:

{
    "email": "rahul@example.com",
    "password": "secret123"
}

The response should contain a JWT token when the credentials are valid.

28. JWT Security Rules

  • Use a strong secret key.
  • Keep the secret key on the server.
  • Never place the secret key inside React Native.
  • Do not put JWT tokens in URLs.
  • Use HTTPS in production.
  • Use short-lived access tokens when appropriate.
  • Do not store passwords inside JWT payloads.
  • Validate authorization on the server.
  • Return HTTP 401 when authentication fails.

29. Complete Project JWT Flow

Registration
      ↓
User Stored in MySQL
      ↓
Login
      ↓
Verify Password
      ↓
Create JWT
      ↓
React Native Stores Token
      ↓
Axios Request
      ↓
Authorization: Bearer JWT
      ↓
PHP Verifies JWT
      ↓
Protected Student API

30. JWT Authentication Summary

JWT authentication is now part of our Student Management project.

  • The login API verifies the user's credentials.
  • PHP creates a JWT after successful authentication.
  • The token contains useful claims such as user ID and expiration.
  • The token is returned to React Native.
  • React Native stores the token for later requests.
  • Axios can send the token using the Authorization header.
  • Protected APIs verify the JWT before processing requests.
  • Expired or invalid tokens should result in HTTP 401.

In the next lesson, we will use the JWT-authenticated user to build the Student API.

📌 Key Points

  • JWT stands for JSON Web Token.
  • A JWT contains a header, payload, and signature.
  • The sub claim can identify the user.
  • The exp claim defines token expiration.
  • firebase/php-jwt can be used to create JWTs in PHP.
  • The JWT secret must remain on the server.
  • React Native should send JWT using the Bearer Authorization header.
  • Protected APIs must verify the JWT.
  • Expired tokens should be rejected.
  • The next lesson will begin the Student API.

🧠 Quick Quiz

Question: Which HTTP header is normally used to send a JWT token to a protected API?