7. Chapter review
Credential management is mostly a handful of habits applied every time. This review collects those habits in one place so you can scan them at the start of the next project and know you've set the right foundations. Two sections of checklist, one of common mistakes, and a short quiz to confirm the pattern is sticking.
The pattern in one paragraph
Use these four steps for every authenticated API: Keep credentials out of code. Read them from the environment. Validate them at startup. Handle 401, 403, and 429 distinctly.
The security checklist
Before writing code
- Create
.gitignorefirst, with.envlisted. - Create
.env.exampledocumenting required variables. Commit it. - Create your local
.envwith actual values. Never commit it. - Confirm the ignore worked:
git statusshould not list.env.
In your code
- Use
os.getenv(), notos.environ[]. Graceful None beats a crypticKeyError. - Validate credentials at startup, before any HTTP work.
- Never hardcode a key, even temporarily. "Temporarily" has a way of becoming "forever".
- Handle 401, 403, and 429 explicitly, each with a message that tells the reader what to do next.
- Retry 429, timeouts, and 5xx errors. Do not retry 401, 403, 400, or 404.
When sharing code
- Scan the file for any
KEY=-shaped string before you paste or screenshot. - Confirm
.envis in.gitignorebefore pushing. A single pre-push glance saves days of cleanup. - If you do expose a key, treat it as compromised and rotate immediately. Assume it's being used.
In production
- Use the platform's secrets manager (AWS Secrets Manager, Heroku Config Vars, Doppler, Vault). Not
.envfiles on the server. - Use separate keys for development, staging, and production, with appropriate permission tiers.
- Rotate keys on a schedule (ninety days is a common cadence) and any time you suspect exposure.
- Watch usage. A key that should handle 100 requests a day and suddenly spikes to 10,000 is telling you something.
Speed matters. The longer the key is public, the more damage. Six steps, in order:
- Assume compromise. Treat the key as "in use by someone else right now", because it quite possibly is.
- Revoke. Go to the API provider's dashboard and deactivate the exposed key immediately.
- Generate a new key. Fresh one, new value, update your local
.env. - Check usage logs. Look at the provider's usage and billing for the exposure window. Unauthorised activity needs to be reported fast.
- Clean Git history. Use
git filter-branchor BFG Repo-Cleaner to remove the key from history. Rotation isn't enough if the old value is still findable. - Tell your team. If others on the project share the codebase or cloud account, they need to know.
The habits worth making automatic
Security works best when it stops requiring conscious effort. Four small automations turn the patterns in this chapter from "things I remember to do" into "things that happen":
- Start every new project from a template that already has
.gitignoreand.env.examplein it. - Install an IDE extension that highlights hardcoded secrets. VS Code has several; GitHub secret scanning runs server-side for free on public repos.
- Add a pre-commit hook that greps for likely key patterns (
pre-commit+detect-secretsorgitleaks). - Make "check for credentials" part of your own code-review checklist, even for solo work. Especially for solo work, no one else is checking.
Common mistakes, and why each one bites
- The "I'll fix it later" trap. Hardcoded "temporarily", then forgotten. Fix it the first time; five minutes of setup saves a day of remediation.
- Creating
.envbefore.gitignore. Onegit add .later and the key is in permanent history. Safety net first, every time. - Screenshots without redaction. The bug you're asking about isn't on line 4; the API key is. Always scan before sharing.
- Production keys in development. A bug or a rate-limit hit in dev now affects production. Separate keys per environment, always.
- Never rotating. Long-lived credentials have more exposure opportunities. Build rotation into your routine before you need it in a panic.
Chapter review quiz
Why is hardcoding API keys dangerous, even in private repositories?
Git history is permanent. Keys remain in history even after you remove them from the current file. Repository access can change, public by accident, acquired by a new company, accessed by former teammates. Repos get forked, cloned, and backed up to places you don't control. What's private today might be public tomorrow. The only safe approach is never committing credentials in the first place.
What's the difference between os.getenv() and os.environ[]?
os.getenv("KEY") returns None if the variable doesn't exist, so your code can handle it gracefully with a clear message. os.environ["KEY"] raises KeyError, which crashes your program with a traceback that doesn't explain what's wrong. Always prefer os.getenv(). It's the same defensive instinct Chapter 6 applied to dict.get(), here applied to the process environment.
Explain the three-file pattern and why each file matters.
.gitignore (committed): tells Git to ignore .env, preventing credential commits. .env.example (committed): documents required variables using placeholders, so teammates know what's needed. .env (never committed): contains real credentials; each developer keeps their own locally. The pattern balances documentation (the team knows what the project needs) with security (nobody's actual secrets leave their machine).
What do 401, 403, and 429 mean, and how should you handle each?
401 Unauthorized means "I don't know who you are", wrong or missing credentials. Fix the key; don't retry.
403 Forbidden means "I know who you are, but you can't do this", your key is valid but this operation isn't permitted. Check your plan tier or request different data; don't retry.
429 Too Many Requests means "you're going too fast", rate limit hit. Respect the Retry-After header if present, otherwise fall back to exponential backoff. This one you can retry, politely.
Why validate credentials at startup instead of at the first request?
Failing fast surfaces configuration problems in a second rather than ten minutes into a run. The error message can be specific ("set OPENWEATHER_API_KEY in your .env") instead of a generic traceback. You avoid wasting CPU, bandwidth, and API quota on work that can't finish. And configuration errors stay cleanly separated from logic bugs, which makes debugging later code much easier.
When should you retry a failed request, and when shouldn't you?
Retry: 429 (rate limits), 5xx (server errors), timeouts, connection errors. These are transient; waiting often fixes them.
Don't retry: 401 (bad credentials), 403 (missing permissions), 404 (wrong resource), 400 (bad request). These don't fix themselves, and retrying wastes quota without getting any closer to success.
Why don't .env files work in production?
.env files are excellent locally but awkward at scale: manual file management across many servers is error-prone, they can't go into version control (but production deployment pipelines live in version control), they have no access controls or audit logs, and rotating keys means editing files on every machine instead of one centralised update. Production uses platform secrets managers (AWS Secrets Manager, Azure Key Vault, Doppler, Vault) that solve all four. The good news: your Python code doesn't change, os.getenv() reads platform-managed variables exactly the same way it reads .env-loaded ones.
How does load_dotenv() work, and why does that matter?
load_dotenv() reads your .env file, parses the NAME=value pairs, and injects them into os.environ at runtime. After that, os.getenv() returns those values as if they'd been set in the shell.
The power of that indirection: the same Python code runs unchanged in development (variables come from .env) and production (variables come from the platform's secrets manager). Nothing is environment-specific; the only thing that changes is where os.getenv() happens to find the value.
Looking forward
Every later chapter that touches a real API, Chapter 8's multi-API weather dashboard, Chapter 14's OAuth flow, Chapter 16's Spotify integration, reuses the patterns in this chapter. The three-file setup, os.getenv() with defaults, startup validation, and 401/403/429 handling become reflexive. When they're reflexive, the next chapter is free to focus on coordination logic and application structure instead of credential management.
Before moving on, one self-check: pick an API you haven't used yet, NewsAPI, GitHub, NASA, OpenAI, whatever appeals, and wire it up from scratch using the patterns from this chapter. .gitignore, .env.example, .env, load_dotenv(), os.getenv(), startup validation, 429 retry. If you can do the whole loop without looking anything up, credential management has moved from knowledge to habit, which is exactly where you want it.