Build a Random Authentication Token in Python

Backend Basics: Authentication Tokens

Part 2 of 6 · Building the random part

A token like session_1002 gives away the pattern. So how do we build one from random characters instead?

In Part 1, we covered why unique doesn’t mean unpredictable. Here’s the Python expression we’ll use for the random part:

import secrets
import string

token = "".join(
    secrets.choice(string.ascii_letters + string.digits)
    for _ in range(37)
)

There’s a lot packed into those few lines. Read them from the inside out.

Start with the characters we allow

string.ascii_letters contains 26 lowercase and 26 uppercase letters. string.digits contains 0123456789.

The + puts these two strings together:

alphabet = string.ascii_letters + string.digits
print(alphabet)
print(len(alphabet))
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
62

That’s our pool: 62 possible characters for each position. Python documents these values as string constants.

Pick one, then do it 37 times

secrets.choice(alphabet) picks one character using randomness suitable for security-sensitive work. One call might return a, another 7, another Q.

for _ in range(37) repeats that choice 37 times. The loop counter runs from 0 to 36, but we don’t need its value. Here, _ is an ordinary variable name that signals “this value is deliberately unused.” It isn’t special loop syntax.

Each choice uses the full alphabet again, so characters can repeat.

Join the choices into one string

The empty string in "".join(...) means “put nothing between the characters.” Compare:

print("".join(["a", "7", "Q"]))
print("-".join(["a", "7", "Q"]))
a7Q
a-7-Q

Our expression uses the first version, with all 37 choices:

flowchart TD A["62 allowed characters"] --> B["Pick one character, 37 times"] B --> C["a · 7 · Q · m · ... · z"] C --> D["Join without separators"] D --> E["a7Qm2Z8pR4tY6uN9bC3dF5gH1jK0sVxWqEoLz"]

That’s an illustrative output, not a value to copy into an application. Running the expression generates a fresh random value; len(token) returns 37. The str.join documentation explains the joining step.

Common misconception: “Random means no repeats”

A token containing aa isn’t broken. Picking a once doesn’t remove it from the pool. The generator also doesn’t promise that two complete tokens can never match; we’ll look at those odds later.

Takeaway: Choose from 62 characters, repeat 37 times, then join. This builds the random part; it doesn’t issue or verify a session by itself.

What would change if you replaced "".join(...) with "-".join(...)?

Next in Part 3: Why use secrets when Python also has a module called random?