Python ships with macOS; install the JWT library for the lab
Greetings, brave adventurer! Every gate worth guarding needs a guardian. An API that anyone can call without identifying themselves is a treasury with its doors flung open. This quest, API Authentication, teaches you to prove who you are when you call an API - and to demand proof when you build one.
You will wield API keys, understand why tokens defeated server sessions in the API era, dissect a JSON Web Token by hand, and walk the full OAuth2 authorization code dance that powers "Log in with Google." Master this, and no protected endpoint can turn you away.
๐ The Legend Behind This Quest¶
Authentication asks "who are you?"; authorization asks "what may you do?" In the early web, servers answered both by remembering you - a session stored in server memory, a cookie in your browser. But APIs are called by phones, scripts, other servers, and third parties, often across organizations. Storing every caller's session on one server does not scale and does not federate.
The answer was the bearer token: a self-contained credential the client carries and presents on every request. Whoever bears the token is granted its access - which is exactly why tokens must travel only over HTTPS and must expire. This quest teaches you to use them safely.
๐ฏ Quest Objectives¶
By the time you complete this journey, you will have mastered:
Primary Objectives (Required for Quest Completion)¶
- Authentication vs Authorization - Distinguish "who you are" from "what you may do"
- API Keys - Use and protect a static API key, and know its limits
- Sessions vs Tokens - Explain why stateless tokens won for APIs
- JSON Web Tokens - Read a JWT's three parts and understand its signature
- OAuth2 and Scopes - Walk the authorization code flow and grant least-privilege scopes
Secondary Objectives (Bonus Achievements)¶
- Refresh Tokens - Trade a short-lived access token for a fresh one without re-login
- PKCE - Understand why public clients need Proof Key for Code Exchange
- Token Storage - Reason about where it is safe to keep a token
Mastery Indicators¶
You'll know you've truly mastered this quest when you can:
- [ ] Authenticate to a real API using a bearer token in an Authorization header
- [ ] Decode a JWT and explain what its signature does and does not guarantee
- [ ] Draw the OAuth2 authorization code flow from memory
- [ ] Choose the right scopes for an integration and justify the minimum
๐บ๏ธ Quest Prerequisites¶
๐ Knowledge Requirements¶
- You have completed API Fundamentals or know HTTP headers and status codes
- You can set headers on a curl request
- You understand that HTTPS encrypts traffic in transit
๐ ๏ธ System Requirements¶
- Modern operating system (Windows 10+, macOS 10.14+, or Linux)
-
curlavailable in your terminal - Optional: Python 3 with
PyJWTfor the token lab - An internet connection
๐ง Skill Level Indicators¶
This ๐ด Hard quest expects: - [ ] You are comfortable with HTTP headers and JSON - [ ] You are ready to reason carefully about security trade-offs - [ ] Ready for 90-120 minutes of focused learning
๐ Choose Your Adventure Platform¶
Authentication concepts are universal. These setups give you tools to decode and forge tokens locally so you can see what is inside them.
Get a token first: the GitHub API calls below read a personal access token from
$GITHUB_TOKEN. See the Novice Challenge: Authenticate for Real near the end of this quest for how to create one (with theread:userscope), then export it before running the curl commands โexport GITHUB_TOKEN=ghp_your_token(PowerShell:$env:GITHUB_TOKEN="ghp_your_token"). Without it, curl sends an empty header and the call is rejected.
๐ macOS Kingdom Path¶
Click to expand macOS instructions
๐ช Windows Empire Path¶
Click to expand Windows instructions
๐ง Linux Territory Path¶
Click to expand Linux instructions
โ๏ธ Cloud Realms Path¶
Click to expand Cloud/Container instructions
# Run the JWT lab in a throwaway Python container
docker run --rm -it python:3.12-slim sh -c "pip install pyjwt && python"
๐ Never commit tokens to source control or paste them into shared chats. Store them in environment variables or a secrets manager.
๐งโโ๏ธ Chapter 1: API Keys and the Authorization Header¶
The simplest credential is the API key: a long random string a service issues you. You attach it to every request, usually in a header. Anyone holding the key acts as you - so treat it like a password.
โ๏ธ Skills You'll Forge in This Chapter¶
- Sending an API key safely
- Why keys are simple but limited
- Authentication vs authorization
๐๏ธ Using an API Key¶
Keys travel in a header (preferred) or sometimes a query parameter (worse, because URLs leak into logs):
# โ
Preferred: key in a header
curl -s -H "Authorization: Bearer YOUR_API_KEY" https://api.example.com/v1/data
# A custom header is also common
curl -s -H "X-API-Key: YOUR_API_KEY" https://api.example.com/v1/data
# โ Avoid: key in the URL - it ends up in server logs and browser history
curl -s "https://api.example.com/v1/data?api_key=YOUR_API_KEY"
API keys identify the application, rarely a specific user, and usually cannot express fine-grained permissions or expire on their own. They are great for server-to-server calls, weak for delegated user access - which is exactly the gap OAuth2 fills.
Authentication is proving identity (the key proves the caller is a known app). Authorization is deciding what that identity may do (whether this app may read or also write).
๐ Knowledge Check: Keys¶
- Why is a key in a header safer than a key in the URL?
- What does an API key typically identify - a user or an app?
- State the difference between authentication and authorization.
โก Quick Wins and Checkpoints¶
- Authenticated a call: You sent a key and received protected data
- Defined the terms: You can separate authn from authz in one sentence
๐งโโ๏ธ Chapter 2: Sessions vs Tokens and the JWT¶
Before APIs, servers tracked you with a session: state in server memory keyed by a cookie. APIs prefer tokens: self-contained credentials the client carries, so the server stays stateless.
โ๏ธ Skills You'll Forge in This Chapter¶
- Why tokens beat sessions for APIs
- The three parts of a JWT
- What a JWT signature does and does not protect
๐๏ธ Sessions vs Tokens¶
| Server session | Bearer token (e.g. JWT) | |
|---|---|---|
| State lives | On the server | In the token itself |
| Scales horizontally | Needs shared session store | Any server can validate |
| Works across domains | Awkward (cookies) | Naturally (header) |
| Revocation | Easy (delete session) | Harder (needs a blocklist or short expiry) |
A JWT (JSON Web Token) has three base64url parts separated by dots: header.payload.signature. The header names the algorithm, the payload holds claims (data), and the signature proves the token was not tampered with.
import jwt # PyJWT
secret = "super-secret-signing-key"
# Issue a token with claims (sub = subject, exp = expiry)
token = jwt.encode(
{"sub": "user-42", "scope": "read:profile", "exp": 1999999999},
secret,
algorithm="HS256",
)
print(token) # eyJhbGciOi... (three dot-separated parts)
# Verify and decode it - raises if the signature or expiry is invalid
claims = jwt.decode(token, secret, algorithms=["HS256"])
print(claims["sub"], claims["scope"]) # user-42 read:profile
Crucially, a JWT payload is encoded, not encrypted - anyone can read it. The signature only proves it has not been altered. Never put secrets in a JWT payload, and always verify the signature server-side. Decode the parts yourself to see this:
# Split a JWT on dots and base64-decode the payload - it is plainly readable
echo "$JWT" | cut -d. -f2 | base64 -d 2>/dev/null
๐ Knowledge Check: Tokens & JWT¶
- Why can any server in a fleet validate a JWT but not a server session?
- What are the three parts of a JWT?
- Is a JWT payload secret? What does its signature actually guarantee?
๐งโโ๏ธ Chapter 3: OAuth2 Flows and Scopes¶
OAuth2 is the framework behind "Log in with Google" and third-party API access. It lets a user grant an app limited access to their data without sharing their password. The most important flow is the authorization code flow.
โ๏ธ Skills You'll Forge in This Chapter¶
- The roles in OAuth2
- The authorization code flow, step by step
- Scopes and least privilege
๐๏ธ The Authorization Code Flow¶
OAuth2 has four roles: the resource owner (the user), the client (the app), the authorization server (issues tokens), and the resource server (the API). The flow:
1. Client redirects user to the authorization server with requested scopes
GET https://auth.example.com/authorize
?response_type=code&client_id=APP&scope=read:profile&redirect_uri=...
2. User logs in and consents to the requested scopes
3. Auth server redirects back to the client with a one-time CODE
GET https://app.example.com/callback?code=ONE_TIME_CODE
4. Client exchanges the code (plus its secret) for tokens, server-to-server
POST https://auth.example.com/token -> { access_token, refresh_token }
5. Client calls the API with the access token
GET /me Authorization: Bearer ACCESS_TOKEN
Step 4 happens on the back channel so the access token never passes through the browser. Exchange the code like this:
# Step 4: trade the one-time code for an access token
curl -s -X POST https://auth.example.com/token \
-d grant_type=authorization_code \
-d code=ONE_TIME_CODE \
-d client_id=APP \
-d client_secret=APP_SECRET \
-d redirect_uri=https://app.example.com/callback
Scopes express least privilege: the client asks only for what it needs (read:profile, not admin:everything). The user sees and approves the exact scopes. A refresh token lets the client obtain a new short-lived access token when it expires, without bothering the user again. For mobile and single-page apps that cannot keep a secret, PKCE (Proof Key for Code Exchange) replaces the client secret with a per-request dynamic proof.
Where you keep a token is a security decision. In a browser, a cookie marked httpOnly, Secure, and SameSite keeps the token out of reach of JavaScript so a cross-site scripting bug cannot steal it (at the cost of needing CSRF protection); storing a token in localStorage is convenient but readable by any script on the page, so one injected script leaks it. On mobile, use the platform secure store (iOS Keychain, Android Keystore), never plain app preferences. On a server, keep tokens and client secrets in environment variables or a secrets manager, never in source control. Everywhere the rule is the same: give the token the shortest lifetime and the smallest blast radius you can.
๐ Knowledge Check: OAuth2¶
- What is the one-time code exchanged for, and why does that happen server-to-server?
- What problem do scopes solve?
- Why do single-page apps need PKCE?
๐ฎ Mastery Challenges¶
๐ข Novice Challenge: Authenticate for Real¶
Objective: Call a protected endpoint using a bearer token.
Requirements:
- [ ] Create a GitHub personal access token with the read:user scope
- [ ] Call https://api.github.com/user with the token in an Authorization header
- [ ] Confirm you receive your own profile, then remove the scope and observe the change
Validation: The authenticated call returns your account; an unscoped call is rejected.
๐ก Intermediate Challenge: Dissect a JWT¶
Objective: Forge and decode a JWT, then tamper with it.
Requirements:
- [ ] Issue a JWT with sub and scope claims using PyJWT
- [ ] Decode the payload by hand (base64) to prove it is readable
- [ ] Change one character of the token and show that verification now fails
Validation: You can explain why tampering is detected but the payload was never secret.
๐ด Advanced Challenge: Diagram the Flow¶
Objective: Document a complete OAuth2 authorization code flow for a real provider.
Requirements: - [ ] Pick a provider (Google, GitHub, Auth0) and list its authorize and token URLs - [ ] Diagram all five steps with the roles labeled - [ ] Choose the minimum scopes for "read the user's email" and justify them
Validation: A teammate could implement the flow from your diagram.
๐ Quest Rewards & Achievements¶
๐๏ธ Badges Earned: - ๐ Keeper of the Keys - You authenticated against a protected API - ๐ก๏ธ Token Warden - You can read and reason about JWTs and OAuth2 flows
๐ ๏ธ Skills Unlocked: - Token-Based Authentication - Carry and present credentials statelessly - OAuth2 Flow Selection - Choose the right grant for the right client
๐ Unlocked Quests:
- Error Handling - Return clean 401/403 responses
- Rate Limiting - Throttle authenticated callers fairly
๐ Progression Points: +75 XP
๐บ๏ธ Next Steps in Your Journey¶
Continue the Main Story: - ๐ฏ Error Handling - Respond to auth failures cleanly
Explore Side Adventures: - โ๏ธ Rate Limiting - Protect your API from abuse - โ๏ธ REST Principles - Design the API you are securing
Character Class Recommendations¶
๐ป Software Developer: Continue to Error Handling ๐๏ธ System Engineer: Explore Rate Limiting ๐ก๏ธ Security Specialist: Advance to Error Handling
๐ Resources¶
Official Documentation¶
- RFC 6749: The OAuth 2.0 Authorization Framework - The OAuth2 spec
- RFC 7519: JSON Web Token (JWT) - The JWT spec
- RFC 7636: PKCE - Proof Key for Code Exchange
Community Resources¶
- OAuth.com - A readable, illustrated OAuth2 guide
- jwt.io - Decode and inspect JWTs in the browser
- OWASP Authentication Cheat Sheet - Secure practices
Learning Materials¶
- MDN: Authorization header - The header you will use constantly
- Auth0: Which OAuth2 flow should I use? - Choosing a grant
- PyJWT documentation - The library used in the lab
๐ค Quest Completion Checklist¶
- โ Completed all primary objectives
- โ Authenticated to a real API with a bearer token
- โ Answered all knowledge check questions
- โ Completed at least one mastery challenge
- โ Explored the resource library
- โ Identified your next quest in the journey
๐ธ๏ธ Knowledge Graph¶
Structured wiki-links connect this quest to the IT-Journey knowledge graph. Open the Obsidian Graph View to explore connections.
Level hub: [[Level 0111 - API Development]] Overworld: [[๐ฐ Overworld - Master Quest Map]] Prerequisites: [[API Fundamentals: HTTP, Requests, and JSON]] Unlocks: [[Error Handling: Status Codes, Problem Details, and Idempotency]] ยท [[Rate Limiting: Token Buckets, 429s, and Quotas]] Obsidian docs: [[Obsidian Knowledge Graph and Wiki Links]]