Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

To protect a PHP endpoint with HTTP Basic Authentication, send a 401 Unauthorized response with a WWW-Authenticate challenge when credentials are missing or invalid. The client retries with an Authorization: Basic … header; PHP makes the submitted username and password available as $_SERVER['PHP_AUTH_USER'] and $_SERVER['PHP_AUTH_PW']. Verify the password against a stored password_hash() value, and serve the endpoint only over HTTPS.

How the PHP Basic Auth flow works

Basic is an HTTP authentication scheme, not a PHP session feature. A client sends a username and password joined by a colon, encodes that value with Base64, and places it in the Authorization request header:

Authorization: Basic <base64(username:password)>

Base64 is an encoding, not encryption. Anyone who can read an unprotected request can recover the credential pair. HTTPS is therefore essential for sensitive use; do not expose a Basic-auth endpoint over plain HTTP.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When the request has no usable credentials, the server responds with status 401 and a WWW-Authenticate header. The header names the scheme and includes a realm, which identifies the protection space. A browser may show its own login prompt; an API client may instead ask the user for credentials or retry programmatically. After the retry, PHP exposes the submitted values through the server variables.

Implement the endpoint with a password hash

The following example expects a PDO database connection configured through environment variables and a users table with a unique username column and a password_hash column sized to at least 255 bytes. Adapt the table or connection settings to your application. It uses a prepared statement for the username lookup, verifies the submitted password with PHP’s password API, and gives unknown users and wrong passwords the same response.

<?php
declare(strict_types=1);

const REALM = 'Admin Area';

function deny(string $message = 'Authentication required'): never
{
    http_response_code(401);
    header('WWW-Authenticate: Basic realm="' . REALM . '", charset="UTF-8"');
    header('Content-Type: text/plain; charset=UTF-8');
    echo $message;
    exit;
}

if (!isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
    deny();
}

$username = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];

$dsn = getenv('DB_DSN');
$dbUser = getenv('DB_USER');
$dbPassword = getenv('DB_PASSWORD');

if ($dsn === false || $dbUser === false || $dbPassword === false) {
    http_response_code(500);
    header('Content-Type: text/plain; charset=UTF-8');
    echo 'Server configuration error';
    exit;
}

try {
    $pdo = new PDO($dsn, $dbUser, $dbPassword, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_EMULATE_PREPARES => false,
    ]);

    $statement = $pdo->prepare(
        'SELECT password_hash FROM users WHERE username = :username'
    );
    $statement->execute(['username' => $username]);
    $user = $statement->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $exception) {
    // Record a suitably protected diagnostic in server-side monitoring.
    http_response_code(500);
    header('Content-Type: text/plain; charset=UTF-8');
    echo 'Authentication service unavailable';
    exit;
}

if ($user === false || !password_verify($password, $user['password_hash'])) {
    deny('Invalid credentials');
}

// Protected application logic starts here.
header('Content-Type: text/plain; charset=UTF-8');
echo 'Authenticated';

The never return type requires PHP 8.1 or later. On an older PHP version, remove : never; the function still exits after sending the response. Ensure the web server does not send page output before this script sets headers.

Configure the database and table

Set DB_DSN, DB_USER, and DB_PASSWORD in the PHP process environment, using the DSN syntax for your PDO driver. Keep secrets outside source control and avoid printing them in error responses. A minimal table shape is:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
CREATE TABLE users (
    username VARCHAR(255) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL
);

Whether usernames compare case-sensitively depends on the database and its collation. Choose and enforce one username policy at account creation and lookup. Do not log the submitted password or return the stored hash to the client.

Create a hash at registration

Hash a password when an account is created or its password is changed, then store the returned string verbatim:

$hash = password_hash($plainTextPassword, PASSWORD_DEFAULT);
// Store $hash in the user's password_hash column.

On sign-in, call password_verify($submittedPassword, $storedHash). Do not store plaintext, manually salt passwords, or hash the submitted value again and compare strings: the generated hash contains the information PHP needs for verification. PHP documents PASSWORD_DEFAULT as bcrypt currently; its documented default bcrypt cost became 12 in PHP 8.4, and the default algorithm may change. A 255-byte column leaves room for future hash formats.

Set the challenge and realm correctly

The response for a missing or invalid credential includes a challenge such as:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Admin Area", charset="UTF-8"

The realm is required by the Basic authentication specification. Pick a stable, meaningful label that tells a user or client what credentials are being requested, such as Admin Area or Internal API. Changing the realm can affect how clients distinguish protection spaces and cache credentials. The optional charset parameter is specified as UTF-8 when used.

Do not build the header from user input. A fixed realm avoids header injection and makes the challenge predictable. PHP also exposes $_SERVER['AUTH_TYPE'] in environments that provide it; the authentication decision in this example relies on the credential variables and the password verification result, not that optional server value.

Test the endpoint over HTTPS

  1. Deploy the endpoint behind a valid TLS configuration and use its HTTPS URL. A redirect from HTTP to HTTPS is useful, but it does not protect credentials sent on the initial HTTP request; do not let clients submit Basic credentials over HTTP.
  2. Request the endpoint without credentials. For example: curl -i https://example.com/private.php. The expected result is status 401 and a WWW-Authenticate: Basic realm="Admin Area" header.
  3. Request it with a known account using curl -i -u alice https://example.com/private.php. Curl prompts for the password when it is omitted after the username. A valid password should reach the protected logic; an invalid password should receive the same generic authentication failure as an unknown username.
  4. Test both success and failure through the actual reverse proxy or web server used in production, not only the PHP development server. Confirm that the password hash is never returned and that no response or application log contains submitted credentials.

Security and operational decisions

Protect every request with TLS

Basic sends the credential pair with each authenticated request in the protection space. HTTPS/TLS protects those credentials in transit between the client and the TLS endpoint. If a reverse proxy terminates TLS, protect the connection from that proxy to PHP as appropriate for the deployment, and configure trusted proxy handling deliberately. Never trust a client-supplied forwarding header as proof that the original connection was secure.

Keep authentication failures generic

Using one failure message for a nonexistent username and an incorrect password avoids revealing which account names exist. Keep detailed database errors and diagnostics on the server side, with access controls and retention appropriate to their sensitivity. Do not include the raw password, Authorization header, or password hash in logs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Plan abuse controls and credential lifecycle

Basic Auth does not itself prescribe a rate limit, lockout policy, rotation schedule, or logging-retention period. Choose those controls for the endpoint’s threat model and user experience. Account for the risk that aggressive lockouts can be abused to deny service, and provide a way to revoke or rotate credentials if they are exposed.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshoot common failures

  • The browser prompts repeatedly. Check that the PHP process receives PHP_AUTH_USER and PHP_AUTH_PW after the retry, and that the username lookup finds the intended account. Confirm that the password was stored as a PHP-generated hash and verified with password_verify().
  • The request never reaches the credential check. A web server, proxy, or FastCGI configuration may not be forwarding the Authorization header to PHP. Inspect the deployed server’s request handling and configure it to pass the header through; the exact setting depends on the server and hosting stack.
  • PHP reports headers already sent. Remove output, whitespace, or a byte-order mark before the opening PHP tag and ensure included files do not emit content before the challenge is sent.
  • The database query fails or every login returns an error. Check the PDO driver, DSN, environment variables, table and column names, and database permissions. Keep the client response generic; use protected server-side diagnostics to find the actual database exception.
  • A valid account gets a 401 after a password change. Confirm the newly generated hash was stored without truncation in a column large enough for 255 bytes, and that the account lookup returns that exact value.
  • Credentials appear to work over HTTP. That is not a safe deployment. Basic credentials are not confidential without TLS, even if the endpoint behaves correctly. Require HTTPS rather than treating successful authentication as evidence of transport security.

Or skip the browser setup

ScreenshotNeo is a separate option for taking website screenshots; it does not replace PHP’s Basic Auth check or make an HTTP endpoint secure. If your task is to capture a public page instead of implementing authentication, its API accepts a URL in one GET request. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

ScreenshotNeo removes cookie/consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Learn about ScreenshotNeo, or sign up for 1,000 free screenshots a month with no card.

Frequently Asked Questions

Can Basic Auth distinguish multiple protected areas on one site?

Yes. Use a meaningful realm for each protection space; clients use the realm as part of identifying which credentials apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does HTTP Basic Authentication create a PHP session?

No. It authenticates requests through the HTTP Authorization header. If the application needs session expiry or a logout workflow, it must provide those separately.

Can a client or browser keep Basic Auth credentials?

Client behavior varies. Do not rely on credential caching as an application-controlled session or logout mechanism.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.