Securely handing out long-lived credentials to developers.
When providing a public API (like Stripe or OpenAI), developers need a way to authenticate their scripts. Instead of giving them a password (which requires manual login flows), you issue a high-entropy string called an API Key. Importantly, you must never store the plain-text API key in your database.
Because an API Key is effectively a permanent password, it must be hashed (using bcrypt/argon2) before hitting the database. The plain-text key is shown to the user only at the moment of creation. To make the key identifiable for partial display or revocation, we often store a prefix alongside the hash.
import secrets
import bcrypt
def issue_api_key(user_id):
# Generate 32 bytes of secure random string
raw_key = secrets.token_urlsafe(32)
# Prepend a prefix to make it identifiable (e.g. Stripe's sk_live_)
api_key = f"sk_live_{raw_key}"
# Hash it for storage
hashed_key = bcrypt.hashpw(api_key.encode(), bcrypt.gensalt())
# Store ONLY the hash and a hint in the DB
db.save(user_id, key_hash=hashed_key, hint=api_key[-4:])
# Show it to the user exactly ONCE
return api_key
def authenticate_request(api_key):
stored_hash = db.get_hash_for_key(api_key[-4:])
if bcrypt.checkpw(api_key.encode(), stored_hash):
return True
return False
Time Complexity: O(1) but deliberately slow due to the hashing algorithm (bcrypt). To prevent this from slowing down API requests, gateways often cache the hashed key in Redis.