Question
How can I generate a random, unique string containing letters and numbers in PHP for an email verification link? For example, after a user creates an account, the application emails a link that the user must open to verify their account.
Short Answer
You will learn how to generate cryptographically secure verification tokens in PHP, why random-looking values are not automatically unique, and how to store, validate, expire, and safely use tokens in email verification links.
Concept
A verification token is a secret, hard-to-guess value that proves a person can access an email inbox. Your application places the token in an email link, then verifies it when the link is opened.
For this job, a token must have two important properties:
- Unpredictability: An attacker must not be able to guess another user's token.
- Uniqueness: Two active verification records must not use the same token.
In PHP, use random_bytes() to create secure random data. It uses a cryptographically secure random number generator (CSPRNG), which is appropriate for passwords, reset links, sessions, and verification links.
A common secure token is a hexadecimal representation of random bytes:
$token = bin2hex(random_bytes(32));
This creates a 64-character string using 0-9 and a-f. Hexadecimal is alphanumeric because it contains letters and numbers.
Although collisions are extraordinarily unlikely with 32 random bytes, your database should still enforce uniqueness. Security-sensitive code should rely on both strong randomness and a database UNIQUE constraint.
Mental Model
Think of a verification token as a randomly cut key for a temporary lock.
random_bytes()is the secure key-cutting machine. Its keys are difficult to copy or predict.- The database is the key registry. It prevents two accounts from receiving the same active key.
- An expiry time is the lock's deadline. After that time, the key no longer works.
- Marking the account as verified is like permanently opening the lock, so the temporary key is no longer needed.
Do not use a predictable counter such as user-42 as the key. Someone could simply try user-43, user-44, and so on.
Syntax and Examples
Use random_bytes() followed by an encoding that is safe to place in a URL.
<?php
$token = bin2hex(random_bytes(32));
echo $token;
// Example shape: 8d1af7c2... (64 hexadecimal characters)
random_bytes(32) returns 32 random binary bytes. Binary data may contain characters that cannot safely be displayed in a URL or stored as normal text. bin2hex() converts every byte to two hexadecimal characters:
- 32 random bytes
- 64 text characters
- Allowed characters:
0-9anda-f
If you specifically want uppercase letters too, you can convert the result:
$token = strtoupper(bin2hex(random_bytes(32)));
The security comes from random_bytes(), not from whether the letters are uppercase or lowercase.
For an email URL, encode the token when building query parameters:
Step by Step Execution
Consider this token-generation code:
<?php
$rawBytes = random_bytes(32);
$token = bin2hex($rawBytes);
$url = 'https://example.com/verify.php?' . http_build_query([
'token' => $token,
]);
echo $url;
Step by step:
random_bytes(32)asks PHP for 32 cryptographically secure random bytes.- Those bytes are raw binary data, so they may contain non-printable values.
bin2hex($rawBytes)converts the binary data into a 64-character hexadecimal string.http_build_query()creates a safe query string such astoken=....- The resulting URL can be sent in an email.
- When the user opens the URL,
verify.phpreads$_GET['token'], finds the matching pending verification record, checks its expiry, and marks the account as verified.
The generated token should be created once, saved, and emailed. Do not generate a new token while attempting to verify the old link.
Real World Use Cases
Secure random tokens are used whenever a URL or value grants temporary access or confirms identity:
- Email verification: Confirm that a new user controls the submitted email address.
- Password reset links: Let a user choose a new password without knowing the old one.
- Invite links: Allow a recipient to join a team, workspace, or event.
- Magic-link login: Sign in a user from an emailed, short-lived link.
- API credentials: Create secrets used by external applications.
- Temporary download links: Authorize limited-time access to a private export or file.
For all of these cases, use secure randomness, a short expiration, and one-time use where appropriate.
Real Codebase Usage
In a production PHP application, token generation is usually only one part of the workflow.
A typical email-verification flow is:
- Create a secure token with
random_bytes(). - Store a hash of the token in the database rather than the raw token.
- Store the user ID, creation time, and expiry time with the hash.
- Email the raw token to the user in an HTTPS link.
- On verification, hash the received token and look up the hash.
- Reject missing, expired, used, or unknown tokens.
- Mark the account verified and delete or invalidate the verification record.
Hashing reduces the impact of a database leak: an attacker who reads the database cannot directly use stored token hashes as verification tokens.
Example database design:
CREATE TABLE email_verifications (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
token_hash CHAR(64) NOT NULL UNIQUE,
expires_at DATETIME NOT NULL,
used_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
A UNIQUE constraint is the final authority for uniqueness. If a collision ever occurs, regenerate the token and try again. In most applications, a 32-byte token makes this event so unlikely that it will not occur in practice, but the constraint protects data integrity.
Also apply guard clauses during verification:
Common Mistakes
Using predictable values
This is not secure:
$token = uniqid();
uniqid() is based on the current time and is not suitable for security tokens. An attacker may be able to predict values generated near a known time.
Use this instead:
$token = bin2hex(random_bytes(32));
Using rand() or mt_rand()
This is also unsuitable for verification links:
$token = mt_rand(100000, 999999);
These functions are fine for non-security tasks such as a visual game effect, but their output is not designed to protect accounts.
Assuming randomness guarantees database uniqueness
Strong randomness makes a duplicate extremely unlikely; it does not replace a database rule. Add UNIQUE to the token hash column and handle an insert failure by generating a new token.
Storing only a token without an expiry
A verification link should not remain valid forever. Store an value and reject old tokens.
Comparisons
| Option | Suitable for email verification? | Why |
|---|---|---|
bin2hex(random_bytes(32)) | Yes | Secure, URL-safe, and contains only hexadecimal letters and digits. |
random_bytes(32) alone | No | Secure, but raw binary data is not suitable as normal URL text. |
base64_encode(random_bytes(32)) | Usually not directly | Secure, but output can include +, /, and = and needs URL-safe handling. |
uniqid() | No | Time-based and predictable enough to be unsafe for security tokens. |
rand() / mt_rand() |
Cheat Sheet
// Create a secure 64-character hexadecimal token.
$token = bin2hex(random_bytes(32));
// Hash before storing in a database.
$tokenHash = hash('sha256', $token);
// Build an email verification URL.
$url = 'https://example.com/verify.php?' . http_build_query([
'token' => $token,
]);
// Hash a token received from the URL before lookup.
$receivedHash = hash('sha256', $_GET['token']);
- Use
random_bytes()for security-sensitive random values. - Use
bin2hex()for a URL-friendly token containing0-9anda-f. - Prefer 32 random bytes for verification and reset links.
- Add a database
UNIQUEconstraint to the stored token hash. - Store an expiry time and invalidate tokens after use.
- Use HTTPS because a token is a temporary secret.
- Do not use
uniqid(), , , timestamps, or sequential IDs for account verification.
FAQ
How long should a PHP email verification token be?
A token created with bin2hex(random_bytes(32)) is a strong default. It is 64 hexadecimal characters and has 256 bits of random input.
Is uniqid() safe for verification links in PHP?
No. uniqid() is not intended for secrets because it is based on timing information and can be predictable.
Is a hexadecimal token alphanumeric?
Yes. A hexadecimal token uses numbers 0-9 and letters a-f. It does not use every letter of the alphabet, but it meets the usual meaning of alphanumeric.
How do I guarantee verification tokens are unique?
Generate tokens with random_bytes() and enforce a UNIQUE database constraint on the stored token or token hash. If insertion fails because of a duplicate, generate another token.
Should I store the raw verification token in my database?
Prefer storing a hash, such as hash('sha256', $token). Send the raw token only to the user, then hash the received token before searching the database.
When should an email verification token expire?
Choose a limited period appropriate for your application, such as 24 hours. Expiry reduces the harm if an old email link is exposed.
Can I use the same token more than once?
For account verification, normally no. Invalidate the token after successful use. A user can request a new link if necessary.
Mini Project
Description
Build a small PHP email-verification-token service. It creates a secure token, stores only its SHA-256 hash with an expiry time, and verifies a token supplied in a URL-like request. The example uses SQLite so it can run as a single file without configuring a separate database server.
Goal
Generate a one-time, expiring verification link and validate it securely.
Requirements
Create a token using random_bytes() and encode it as hexadecimal.|Store a SHA-256 hash of the token instead of the raw token.|Prevent duplicate stored token hashes with a database unique constraint.|Reject missing, invalid, expired, and previously used tokens.|Mark a valid token as used after verification.
Keep learning
Related questions
Are PDO Prepared Statements Enough to Prevent SQL Injection in PHP?
Learn how PDO prepared statements prevent SQL injection in PHP, what they protect, and the mistakes that still leave MySQL apps vulnerable.
Can You Bind an Array to an IN Clause in PHP PDO?
Learn how PDO handles placeholders in IN() clauses, why arrays cannot be bound directly, and the safe PHP pattern to build dynamic queries.
Choosing the Right MySQL Collation for PHP and UTF-8
Learn how MySQL character sets and collations work with PHP, and how to choose a practical UTF-8 setup for web applications.