CodeBlanks Logo Icon CodeBlanks

Challenge - Security app

PHP course · Advanced · Lesson 89/92

Build a full authentication flow with password_hash, CSRF and security headers.

Authentication flow

In this lesson you write the AuthService class. password_hash($pass, PASSWORD_DEFAULT) hashes the password with bcrypt; the salt is generated automatically. password_verify($pass, $hash) checks the hash and is safe against timing attacks.

The class has three more methods. csrfToken() produces a 16-character string with bin2hex(random_bytes(8)) on first call and stores it. applyHeaders() sends X-Frame-Options: DENY and X-Content-Type-Options: nosniff headers via header(). This adds a security layer alongside the API response.

Security headers

HTTP response headers tell the browser how to handle the response. X-Frame-Options: DENY stops the page from being framed on another site (clickjacking protection). X-Content-Type-Options: nosniff prevents the browser from sniffing the MIME type.

In PHP the header("Name: Value") call sends these headers. They produce no output in CLI mode but are added to the response headers on a web server. The applyHeaders() method sends every header from one place, so you never forget to turn protection on. You also avoid repeating the same headers on each request.

Exercise Write the AuthService class

The starting code has the Product API ready. Now add the AuthService class. Give it private array $users = [] and string $csrf = "" properties.

The register(string $user, string $pass): void method hashes with password_hash($pass, PASSWORD_DEFAULT) and stores it in $users. login(string $user, string $pass): bool returns false if the user is missing, otherwise the result of password_verify. csrfToken(): string generates a token with bin2hex(random_bytes(8)) on first call. applyHeaders(): void sends X-Frame-Options: DENY and X-Content-Type-Options: nosniff via header(). Run the demo.

Show example solution
Define class AuthService { private array $users = []; private string $csrf = ""; ... }. Use password_hash in register, password_verify in login, bin2hex(random_bytes(8)) in csrfToken and two header() calls in applyHeaders. Then run the demo.
Example solution:
<?php
// Product API + security: write AuthService

class ProductRepository {
    private PDO $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
        $this->pdo->exec("CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price INTEGER)");
        $stmt = $this->pdo->prepare("INSERT INTO products (name, price) VALUES (?, ?)");
        $stmt->execute(["Kale", 5]);
        $stmt->execute(["Defter", 15]);
    }

    public function all(): array {
        $rows = $this->pdo->query("SELECT id, name, price FROM products ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
        return array_map(fn($r) => [
            "id" => (int)$r["id"],
            "name" => $r["name"],
            "price" => (int)$r["price"]
        ], $rows);
    }
}

function handleGet(PDO $pdo): string {
    $repo = new ProductRepository($pdo);
    return json_encode(["products" => $repo->all()], JSON_UNESCAPED_UNICODE);
}

class AuthService {
    private array $users = [];
    private string $csrf = "";

    public function register(string $user, string $pass): void {
        $this->users[$user] = password_hash($pass, PASSWORD_DEFAULT);
    }

    public function login(string $user, string $pass): bool {
        if (!isset($this->users[$user])) return false;
        return password_verify($pass, $this->users[$user]);
    }

    public function csrfToken(): string {
        if ($this->csrf === "") {
            $this->csrf = bin2hex(random_bytes(8));
        }
        return $this->csrf;
    }

    public function applyHeaders(): void {
        header("X-Frame-Options: DENY");
        header("X-Content-Type-Options: nosniff");
    }
}

// Demo:
$auth = new AuthService();
$auth->register("ayse", "secret password");
echo $auth->login("ayse", "secret password") ? "login: ok" : "login: denied";
echo "\n";
echo $auth->login("ayse", "wrong") ? "login: ok" : "login: denied";
echo "\n";
echo strlen($auth->csrfToken()) . "\n";
$auth->applyHeaders();
echo "headers: ok\n";

Test yourself

  1. Complete the password_verify call

    return ____($pass, $this->users[$user]);
    Show answer
    return password_verify($pass, $this->users[$user]);
  2. Which function is used to generate a CSRF token?

    • random_bytes with bin2hex
    • md5 with a timestamp
    • password_hash with bcrypt
    • sha1 with the username
    Show answer

    random_bytes with bin2hex — Correct. random_bytes produces cryptographically secure random bytes; bin2hex turns them into a string.

  3. Match security concepts

    password_hash
    Stores password with bcrypt
    password_verify
    Checks password against hash
    bin2hex
    Converts bytes to hex string
    header
    Sends HTTP response header

All lessons in Advanced