Code of the Day
IntermediateExternal Integrations

Authentication

Most APIs require credentials. Learn the three common authentication patterns and why storing secrets in environment variables — not source code — is non-negotiable.

WorkflowIntermediate6 min read
By the end of this lesson you will be able to:
  • Distinguish API key in header, API key as query param, and OAuth 2.0 bearer token flows
  • Explain why hardcoding credentials in source code is dangerous
  • Use environment variables and a .env file as the standard safe alternative

Almost every API beyond the purely public ones requires you to prove who you are before it will respond. That proof takes several forms. Knowing which pattern an API uses — and how to pass credentials safely — is a practical necessity before you write a single line of integration code.

Pattern 1: API key in a header

The most common pattern. You include your key in a request header, typically Authorization:

Authorization: Bearer sk-abc123...

Some APIs use a custom header name instead: X-API-Key: sk-abc123.... The API documentation always specifies which header to use and what prefix (if any) to add before the key value.

In Python with requests:

headers = {"Authorization": "Bearer sk-abc123..."}
response = requests.get("https://api.example.com/data", headers=headers)

Pattern 2: API key as query parameter

Older or simpler APIs embed the key directly in the URL:

https://api.example.com/data?api_key=sk-abc123...

This works, but it is less secure: query parameters appear in server logs, browser history, and proxy logs. Prefer the header approach when you have a choice.

Pattern 3: OAuth 2.0 bearer tokens

OAuth is a token-exchange protocol. You do not pass your API key directly; instead you exchange credentials for a short-lived access token, then use that token as a bearer credential in each request. The exact exchange flow varies (client credentials, authorization code, device flow), but the end result is the same: you get a token, you include it in the Authorization header, you refresh it when it expires.

OAuth is the pattern used by Google, GitHub, Slack, and most large platforms. For scripting purposes, many of these platforms also offer long-lived personal access tokens that behave like API keys — simpler to use for automation, but with the same security obligations.

Why hardcoding credentials is dangerous

The temptation is to write:

API_KEY = "sk-abc123..."   # do not do this

The moment that file enters a git repository — even a private one — the key is in the commit history. History cannot be easily scrubbed. If the repository ever becomes public, or if someone clones it who should not have access, the key is exposed. API key leaks are one of the most common causes of unauthorised cloud charges and data breaches.

Treat an API key exactly like a password. You would not hardcode your database password in a source file — apply the same rule to every credential.

The solution: environment variables and .env files

Environment variables live outside the source tree:

import os

api_key = os.environ.get("MY_API_KEY")
if not api_key:
    raise RuntimeError("MY_API_KEY environment variable is not set")

For local development, .env files are the standard convenience: a plain text file listing KEY=value pairs that you add to .gitignore immediately. The python-dotenv library loads the file into os.environ at startup:

from dotenv import load_dotenv
import os

load_dotenv()   # reads .env in the current directory
api_key = os.environ.get("MY_API_KEY")

Your .gitignore should always contain .env. The .env.example file (with placeholder values, no real secrets) is what you commit so teammates know which variables are expected.

Where to go next

Next: auth in practice — putting the header pattern and environment variable loading together in a runnable example, including what a 401 response looks like when credentials are missing.

Finished reading? Mark it complete to track your progress.

On this page