Generate Secure Random Tokens
Create unpredictable URL-safe tokens with an explicit minimum random-byte requirement.
Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons
Video lesson: Generate Secure Random Tokens
The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube
Jump to a chapter
Read the video transcript
Select a timestamp to watch that moment on YouTube.
- 00:00
Consider a link that lets someone reset an account password. The token in that link must be difficult for another person to guess. Python has a `random` module, and it is excellent for simulations, games, and repeatable tests. But its generator is designed to be reproducible from a seed, not to issue security credentials. In this lesson we will prove that difference with a local program, then create tokens with the standard-library `secrets` module. We will choose the number of random bytes deliberately, format a token that can fit in a URL, and compare two candidate values with `compare_digest`. No token in this video protects an actual account.
- 00:42
We only print safe demonstrations, and you should never paste real credentials into a tutorial console. The useful question is not whether a generated string looks complicated. It is whether the source of randomness and the way the token is handled match the security job. At the end, we will verify the output's shape without claiming that one short test proves unpredictability. Both calls start a generator from the same seed. Their first sixteen choices are the same, so the tokens are identical. That property is valuable when debugging a simulation: a failing run can be replayed. It is the wrong property for a password reset token.
- 01:24
An attacker may not know the exact seed, but the important point is that this generator has a predictable internal sequence and is not intended for adversarial use. Removing the visible seed from this function would not turn `random` into a cryptographic source. Likewise, increasing the token length would not repair the underlying choice of generator. The output shown is only a toy token, not a credential to reuse. We should preserve `random` for reproducible experiments and switch to `secrets` when the value's secrecy matters. The source of the random bits comes first; presentation as letters, digits, hex, or URL-safe text comes after.
- 02:06
Keep those concerns separate when reviewing a token-issuing function. We request sixteen random bytes in both calls. Hexadecimal turns every byte into two printed characters, so the hex token has thirty-two characters. The URL-safe function encodes the random bytes in a form that avoids spaces and ordinary slash characters, which is convenient for a link or a form value. Its visible length is not necessarily sixteen characters; text encoding and the byte count are different measurements. The regular-expression checks describe the output format, not its security strength. A token made of thirty-two zeros would also fit the hex pattern but would be
- 02:48
completely guessable. For a real reset token, choose a byte count that meets the product's security requirements, store and transmit it carefully, set an expiry, and make it single-use. The standard library provides the randomness; the surrounding application still has to manage the token's lifecycle. We also avoid printing the actual `secrets` token here because copying a real token into logs can expose it even when it was generated correctly. `issue_token` asks the operating system's cryptographic random source for bytes through `secrets`, then returns a URL-safe string. The exact token is deliberately not displayed.
- 03:31
We check that it has a plausible encoded length and test matching behavior with `compare_digest`. A correct candidate returns True; changing even one character produces False. `compare_digest` is designed to avoid a simple content-dependent timing comparison when two values have comparable types. It is useful when checking secrets, but it is not a complete authentication system on its own. In a production service, one would normally store a protected representation of the token, associate it with the intended user and purpose, and expire or invalidate it after use. Those storage and lifecycle details depend on the application, so this lesson keeps
- 04:12
the runnable example small and focused. Notice that we do not seed `secrets`. A security token should not be replayable by picking a known seed. If your test needs a deterministic stand- in, inject a separate fake token generator into the test rather than weakening the real issuer. This run verifies the behavior we can see without knowing the generated values in advance. All four tokens are different in this sample, their characters are URL-safe, and the comparison function accepts an exact match while rejecting a different candidate. Four unique values are not proof of cryptographic security; this is only a
- 04:53
functional smoke test. The security claim comes from choosing the `secrets` module for this kind of token and from a sound surrounding protocol. The number of requested bytes, expiry, single-use handling, transport security, and how the token is stored all matter. Try changing the byte count in the lesson console and compare the length of `token_hex` with `token_urlsafe`; do not confuse either printed length with random- byte count. Then intentionally replace `secrets` with two seeded `Random` instances and observe the repeated result from the beginning. That contrast is the lesson: deterministic random generation helps reproduce a
- 05:36
simulation, while a reset token must resist prediction. Complete the practice task and quiz to decide which generator belongs in each situation.
Understand the concept
A password-reset or invitation token must be difficult to guess. The random module serves simulations and reproducible sequences; if a seed is known, its output can be replayed. Python's secrets module draws on operating-system randomness for security-sensitive values. Keep these use cases separate even when a fixed seed would make a test easier.
secrets.token_urlsafe(nbytes) produces URL-safe text from nbytes random bytes. Sixteen bytes carry 128 bits before encoding and yield 22 unpadded Base64 URL characters. Character count measures the encoding, not the entropy. This lesson requires at least 16 random bytes as an exercise rule; a production service must follow its own threat model and current security guidance.
Treat a token like a credential. Do not print it to application logs or publish it in screenshots. A demo can report its length and allowed alphabet instead. secrets.compare_digest helps compare same-type secret values without content-dependent timing, but cannot repair leaked tokens or replace expiration, one-time use, and rate limits.
Implement make_token with a default of 16 bytes. Reject booleans, other non-integers, and smaller counts. The checks inspect shape and minimum length; they do not assert that two generated tokens differ, because a collision is theoretically possible. No browser exercise here stores or delivers real credentials.
- secrets is for security-sensitive randomness
- nbytes is random input bytes
- An encoded token is still a credential
- compare_digest is for comparing secret values
See it step by step
Read the code, predict the output, then compare it with the result.
01. Check a token without revealing it
import secrets
token = secrets.token_urlsafe(16)
print(len(token))
print(all(ch.isalnum() or ch in '-_' for ch in token))22
TrueShape is testable without disclosing the secret value.
02. Distinguish input bytes from hex characters
import secrets
token = secrets.token_hex(4)
print(len(token))
print(all(ch in '0123456789abcdef' for ch in token))8
TrueFour random bytes become eight hexadecimal characters.
03. See why a fixed seed is wrong for credentials
import random
a = random.Random(7)
b = random.Random(7)
print(a.random() == b.random())TrueThe same seed reproduces the first draw. That is useful for tests, not for secret tokens.
A closer look
Follow the reasoning, inspect each result, then try the suggested changes in the console below.
Test the shape without exposing the secret
The first example creates a URL-safe token from sixteen random bytes. The run reports a length of twenty-two characters and True for an alphabet check, without printing the secret itself. Encoding changes the number of displayed characters; the input byte count is what determines the random-bit budget in this example.
A production token also needs a safe lifecycle: delivery to the intended recipient, expiration, one-time use when appropriate, and protection from logs. A classroom shape check cannot prove unpredictability by itself. It does let the checked function validate that it returned usable URL-safe text without leaking a credential into screenshots or test output.
import secrets
token = secrets.token_urlsafe(16)
print(len(token))
print(all(ch.isalnum() or ch in '-_' for ch in token))22
True- Generate with secrets.token_urlsafe and an explicit byte count.
- Check length and allowed characters only.
- Keep the full token out of logs and screenshots.
Separate random bytes from encoded characters
The second run asks token_hex for four random bytes and gets eight hexadecimal characters. Each byte becomes two hex digits. That simple conversion helps explain why a displayed token's length is not the same as its entropy in bytes. URL-safe Base64 uses a different encoding and a different visible length.
The exercise sets a minimum of sixteen random bytes for its own contract. Reject smaller values, booleans, and non-integers before calling the generator. A caller cannot increase the entropy merely by repeating or padding a shorter string. Choose a generation API and byte count for the actual security requirement, then store the token securely.
import secrets
token = secrets.token_hex(4)
print(len(token))
print(all(ch in '0123456789abcdef' for ch in token))8
True- Observe the two hex characters per byte.
- Validate the requested byte count before generation.
- Do not equate character count with random-byte count.
Keep reproducible randomness away from credentials
Two independent random.Random instances with the same seed produce the same first value in the last example. That is useful when a simulation must be replayed for a test, but it is unsuitable for reset links or invitation credentials. A known seed lets another party recreate the sequence rather than guess it.
Use secrets for security-sensitive tokens and random.Random for repeatable experiments. Do not write a test that demands two real tokens differ: a collision is possible in principle even though an adequately sized token makes it extremely unlikely. Test the interface and validation rules instead, and keep generated values private throughout the workflow.
import random
a = random.Random(7)
b = random.Random(7)
print(a.random() == b.random())True- Replay two equal-seed pseudorandom draws.
- Contrast that property with a credential's unpredictability requirement.
- Test token format and input validation without logging the token.
Try it in Python
Edit the example and run it. Python starts in your browser the first time you click Run.
Python console
Ready to runNeed input()? Add one value per line
Your output appears here.
Need a hint?
Import secrets. Check type(nbytes) is int and nbytes >= 16, then return secrets.token_urlsafe(nbytes).
Complete the task and select Check task to verify your code.
Quick quiz
Three questions. You can change your answers and try again.
Typical mistakes
Everyone meets these errors. See what causes them and how to fix them.
Seeding a token generator
random.seed(7)
token = str(random.random())token = secrets.token_urlsafe(16)What happens: Anyone with the seed can reproduce the token.
Use security-oriented randomness and enough random bytes.
Logging the credential
print('issued token', token)print('token issued; characters:', len(token))What happens: A copied log now contains a usable secret.
Show harmless metadata when debugging, not the credential.