Computing Foundations › APIs and the Web › Day 25
Hands-on lab — Day 25: API Authentication: Keys, Tokens, and OAuth
- ← Back to the Day 25 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/computing-foundations/day-025-api-authentication-keys-tokens-and-oauth/
Commands
Setup
cd labs/sections/computing-foundations/day-025-api-authentication-keys-tokens-and-oauth Run
bash examples/auth_demo.sh
bash starter/auth_demo.sh Test
bash tests/run_tests.sh File tree
examples/auth_demo.sh expected-output/FIELDS.md expected-output/sample-run.txt metadata.yml README.md requirements/README.md security.md starter/auth_demo.sh starter/auth-worksheet.md tests/run_tests.sh troubleshooting.md
Lab README
Day 025 lab — Authenticate to an API
Lesson
- Lesson title: API Authentication: Keys, Tokens, and OAuth
- Day number: 25 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-025-api-authentication-keys-tokens-and-oauth
- Lab files: everything you need is in this directory — follow “How to run” below.
- Browse the course locally: from the repository root, this lab also appears in the course website at
/labs/day-025-api-authentication-keys-tokens-and-oauthwhen the site is running.
Purpose
Day 25's lesson explains how APIs verify who is calling. This lab makes it
concrete: you use curl to send each common authentication scheme to a public
test server and watch it succeed and fail. You will see Basic auth return 200
for the right password and 401 for the wrong one, send a bearer token, echo a
key header back off the server, and — the habit that matters most — read a
token from an environment variable instead of typing it inline. Every
credential here is an obviously fake placeholder, and the test server accepts
any of them, so no API key and no account are required.
Learning objectives
- Send Basic auth, a bearer token, and an API key header with
curl. - Read authentication outcomes as HTTP status codes (
200success,401a missing or wrong credential). - Confirm a bearer token is accepted and echoed by the server.
- Read a credential from an environment variable so the secret never appears inline — the safe pattern you will use with every real key.
- Run an automated test that degrades gracefully offline and exits 0.
Prerequisites
- The Day 25 lesson (read it first — it explains every scheme this lab sends).
- Day 18 (HTTP status codes) and Day 11 (environment variables) help.
- A terminal with
curl: Terminal.app (macOS), any terminal (Linux), or WSL/Git Bash (Windows). - No programming experience required; every command is given and explained.
Supported operating systems
- macOS — fully supported (tested on macOS with curl 8.7.1).
- Linux — fully supported (any distribution with
curl). - Windows — run the scripts unchanged in WSL or Git Bash.
Hardware requirements
Any computer that can run a terminal and reach the internet. The lab only sends small HTTP requests; it needs no minimum RAM, disk, or GPU.
Required software
bash(3.2 or newer — preinstalled on macOS and Linux).curl(preinstalled on macOS and most Linux;sudo apt install curlon Debian/Ubuntu). Verify withcurl --version.
Free and open-source options
Everything in this lab is free: bash and curl are open-source or ship with
your OS, and httpbin.org is a free public test service. No account, API key,
or purchase is needed — the endpoints accept any (fake) credentials on purpose.
Installation
None. Clone the repository (or copy this directory) and you are ready:
cd labs/sections/computing-foundations/day-025-api-authentication-keys-tokens-and-oauth
File structure
day-025-api-authentication-keys-tokens-and-oauth/
├── README.md ← you are here
├── metadata.yml ← machine-readable lab metadata
├── starter/
│ ├── auth_demo.sh ← YOUR working file (4 exercises)
│ └── auth-worksheet.md ← worksheet for the practice assignment
├── examples/
│ └── auth_demo.sh ← completed reference implementation
├── tests/
│ └── run_tests.sh ← automated checks (skip gracefully offline)
├── expected-output/
│ ├── sample-run.txt ← real captured run (macOS, online)
│ └── FIELDS.md ← required output lines on every platform
├── requirements/
│ └── README.md ← dependency statement (curl only)
├── troubleshooting.md
└── security.md
How to run
From this directory:
## 1. See the finished result first
bash examples/auth_demo.sh
## 2. Your task: complete the four exercises in the starter, then run it
bash starter/auth_demo.sh
## 3. Check your work
bash tests/run_tests.sh
What the commands do
bash examples/auth_demo.sh— runs the reference script: it checks connectivity, then sends Basic auth with correct credentials (expecting200) and wrong credentials (expecting401), a bearer token in theAuthorizationheader, anX-API-Keyheader the server echoes back, and a bearer token read from theDEMO_TOKENenvironment variable. Offline, it announces the skip and exits 0.bash starter/auth_demo.sh— the same skeleton with four exercises left as "NOT DONE YET" placeholder echoes; each comment names the exactcurlcommand to run. Edit the file in any text editor and replace each placeholder line with the command given.bash tests/run_tests.sh— runs structure checks (files present, the example exercises every scheme, the starter names its exercises, both scripts parse, no realistic long key-shaped string is present) and, when online, network checks: correct Basic auth returns200, wrong credentials return401, and the bearer endpoint returns200. Offline or during a transient httpbin503, the network checks are skipped, not failed. Exits 0 unless a structure check fails.
Expected output
See expected-output/sample-run.txt — a real
captured run. The key lines:
--- 1. Basic auth with CORRECT credentials (expect 200) ---
HTTP status: 200
--- 2. Basic auth with WRONG credentials (expect 401) ---
HTTP status: 401
--- 3. Bearer token in the Authorization header ---
{
"authenticated": true,
"token": "token-example-123"
}
Your X-Amzn-Trace-Id value differs on every request — httpbin assigns it.
expected-output/FIELDS.md lists exactly which
lines must appear on every platform.
Validation steps
- Run
bash examples/auth_demo.sh— it must reach=== End of demo ===without errors. - Confirm Basic auth printed
200for correct credentials and401for wrong ones. - Confirm the bearer endpoint returned
"authenticated": trueand echoed your token. - Complete
starter/auth_demo.sh(no "NOT DONE YET" placeholder lines left) and run it — it must produce the same statuses. - Run the tests (next section) — all structure checks must pass and the script must exit 0.
Tests
bash tests/run_tests.sh
The command exits 0 on success (no structure check failed), whether online or
offline, so it can run in CI. Online with httpbin healthy, the final line reads
16 checks, 0 failure(s), 0 skip(s).; when httpbin is transiently overloaded,
the three network checks are skipped (13 checks, 0 failure(s), 3 skip(s).) and
the script still exits 0 — a server outage is not your bug.
Cleanup
Nothing to clean up: the scripts only send HTTP requests and write no files. To
reset your work, restore the starter from git:
git checkout -- starter/auth_demo.sh.
Troubleshooting
See troubleshooting.md for the full list (000 and 503
responses, Basic auth 401, empty $DEMO_TOKEN, missing curl, Windows).
Security notes
See security.md. Short version: this lab uses only fake
credentials against a public test server. Never put a real API key in a
script or commit one — store real keys in an environment variable or a
gitignored .env, use least privilege, and rotate any leaked key immediately.
Extension exercises
- Prove base64 is not encryption:
printf 'user:pass' | base64then pipe the result tobase64 --decodeand watchuser:passcome straight back with no key. - Send the
X-API-Keyin the URL query string instead of a header (https://httpbin.org/get?api_key=demo-key), see it echoed underargs, and write one sentence on why that placement leaks the key into logs. - Verify a missing credential fails:
curl -s -o /dev/null -w '%{http_code}\n' https://httpbin.org/bearerand confirm you get401with no token sent.
Navigation
- Previous day: Day 24 — JSON and Data Serialization
(
labs/sections/computing-foundations/day-024-json-and-data-serialization/). - Next day: Day 26 — Webhooks and Event-Driven APIs
(
labs/sections/computing-foundations/day-026-webhooks-and-event-driven-apis/, to be written).
Expected output
FIELDS.md
# Required output lines (all platforms)
A correct online run of `examples/auth_demo.sh` prints, in order:
1. `=== API Authentication Demo ===`
2. Basic auth, correct credentials: `HTTP status: 200`
3. Basic auth, wrong credentials: `HTTP status: 401`
4. Bearer endpoint: JSON with `"authenticated": true` and `"token": "token-example-123"`
5. Headers endpoint: JSON echoing your `X-Api-Key: demo-key` (httpbin normalizes the
header name's capitalization to `X-Api-Key`)
6. Env-var bearer: JSON with `"authenticated": true` and `"token": "token-from-env-example"`
7. `=== End of demo ===`
`sample-run.txt` in this directory is a real captured run (macOS, curl 8.7.1,
online, 2026-07-12).
## Platform notes
- **Linux** produces byte-for-byte the same output; only the `User-Agent`
string (the local curl version) and the `X-Amzn-Trace-Id` value differ.
- **Windows** users run the same commands in WSL or Git Bash; native
PowerShell uses `Invoke-RestMethod` with `-Headers`, which is out of scope
for this lab.
- The `X-Amzn-Trace-Id` value is assigned fresh by httpbin on every request,
so it will never match the sample exactly — that is expected.
- If any request prints `503`, httpbin is transiently overloaded (a shared
free service). That is the server, not your credential; retry shortly. The
test script treats a transient 5xx as a SKIP, not a failure.
sample-run.txt
Real captured run of `bash examples/auth_demo.sh`
(macOS, curl 8.7.1, online, 2026-07-12). Your X-Amzn-Trace-Id value will
differ on every request — it is assigned by httpbin, not by you.
=== API Authentication Demo ===
Target: https://httpbin.org (public test server; accepts any credentials)
--- 1. Basic auth with CORRECT credentials (expect 200) ---
$ curl -u user:pass https://httpbin.org/basic-auth/user/pass
HTTP status: 200
--- 2. Basic auth with WRONG credentials (expect 401) ---
$ curl -u user:wrongpass https://httpbin.org/basic-auth/user/pass
HTTP status: 401
--- 3. Bearer token in the Authorization header ---
$ curl -H 'Authorization: Bearer token-example-123' https://httpbin.org/bearer
{
"authenticated": true,
"token": "token-example-123"
}
--- 4. API key in a custom header, echoed back by the server ---
$ curl -H 'X-API-Key: demo-key' https://httpbin.org/headers
{
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/8.7.1",
"X-Amzn-Trace-Id": "Root=1-6a538ecc-412e97e705dd106650616580",
"X-Api-Key": "demo-key"
}
}
--- 5. Bearer token READ FROM AN ENVIRONMENT VARIABLE (the safe pattern) ---
$ export DEMO_TOKEN=... (kept out of code and git)
$ curl -H "Authorization: Bearer $DEMO_TOKEN" https://httpbin.org/bearer
{
"authenticated": true,
"token": "token-from-env-example"
}
=== End of demo ===
--- Offline behavior ---
With no network, the script prints:
=== API Authentication Demo ===
Target: https://httpbin.org (public test server; accepts any credentials)
No network access — skipping the live requests.
Online, this script sends Basic auth, a bearer token, and a key header to https://httpbin.org.
=== End of demo ===
Source files
examples/auth_demo.sh (2755 bytes)
#!/usr/bin/env bash
# Day 025 lab — completed reference implementation.
# Authenticate to a public test API (httpbin.org) with each common scheme:
# 1. Basic auth, correct credentials -> 200
# 2. Basic auth, wrong credentials -> 401
# 3. Bearer token in the Authorization header
# 4. An API key in a custom header, echoed back
# 5. A bearer token read from an environment variable (the safe pattern)
#
# httpbin's auth endpoints accept ANY credentials, so no real key is needed.
# Every credential in this script is an obviously fake placeholder.
# If the network is unavailable, the script says so and exits 0 (nothing to prove offline).
set -uo pipefail
API="https://httpbin.org"
# curl options: fail quietly on server errors, cap the wait, ride past transient 5xx.
CURL=(curl -s --max-time 25 --retry 5 --retry-delay 2)
echo "=== API Authentication Demo ==="
echo "Target: ${API} (public test server; accepts any credentials)"
echo
# Bail out gracefully when offline so the demo never hangs or errors.
if ! curl -s -o /dev/null --max-time 12 "${API}/status/200" 2>/dev/null; then
echo "No network access — skipping the live requests."
echo "Online, this script sends Basic auth, a bearer token, and a key header to ${API}."
echo "=== End of demo ==="
exit 0
fi
echo "--- 1. Basic auth with CORRECT credentials (expect 200) ---"
echo "\$ curl -u user:pass ${API}/basic-auth/user/pass"
code="$("${CURL[@]}" -o /dev/null -w '%{http_code}' -u user:pass "${API}/basic-auth/user/pass")"
echo "HTTP status: ${code}"
echo
echo "--- 2. Basic auth with WRONG credentials (expect 401) ---"
echo "\$ curl -u user:wrongpass ${API}/basic-auth/user/pass"
code="$("${CURL[@]}" -o /dev/null -w '%{http_code}' -u user:wrongpass "${API}/basic-auth/user/pass")"
echo "HTTP status: ${code}"
echo
echo "--- 3. Bearer token in the Authorization header ---"
echo "\$ curl -H 'Authorization: Bearer token-example-123' ${API}/bearer"
"${CURL[@]}" -H "Authorization: Bearer token-example-123" "${API}/bearer"
echo
echo "--- 4. API key in a custom header, echoed back by the server ---"
echo "\$ curl -H 'X-API-Key: demo-key' ${API}/headers"
"${CURL[@]}" -H "X-API-Key: demo-key" "${API}/headers"
echo
echo "--- 5. Bearer token READ FROM AN ENVIRONMENT VARIABLE (the safe pattern) ---"
# The secret lives in a variable, not inline in the command. Here it is a fake value;
# for a real API you would 'export' the key from a gitignored .env, never hard-code it.
export DEMO_TOKEN="token-from-env-example"
echo "\$ export DEMO_TOKEN=... (kept out of code and git)"
echo "\$ curl -H \"Authorization: Bearer \$DEMO_TOKEN\" ${API}/bearer"
"${CURL[@]}" -H "Authorization: Bearer ${DEMO_TOKEN}" "${API}/bearer"
echo
echo "=== End of demo ==="
metadata.yml (739 bytes)
lesson_id: D025
day: 25
kind: api-example
languages: [bash]
setup_commands:
- cd labs/sections/computing-foundations/day-025-api-authentication-keys-tokens-and-oauth
run_commands:
- bash examples/auth_demo.sh
- bash starter/auth_demo.sh
test_commands:
- bash tests/run_tests.sh
cleanup_commands:
- 'git checkout -- starter/auth_demo.sh # optional: reset your work'
requires_network: true
requires_api_key: false
estimated_minutes: 30
last_executed: '2026-07-12'
executed_on: 'macOS (curl 8.7.1), online, bash tests/run_tests.sh → 16 checks, 0 failure(s), 0 skip(s); example run gave Basic 200 / Basic 401 / bearer authenticated:true. When httpbin returns a transient 503 the network checks SKIP and the script still exits 0.'
requirements/README.md (794 bytes)
# Dependencies — Day 025 lab
**Only `curl`.** This lab has no installable dependencies beyond a POSIX
shell and the `curl` command:
- `bash` ≥ 3.2 (preinstalled on macOS and every mainstream Linux distribution)
- `curl` (preinstalled on macOS and most Linux; on Debian/Ubuntu install with
`sudo apt install curl`, on Fedora with `sudo dnf install curl`)
Check that curl is present:
```bash
curl --version
```
There is deliberately no `requirements.txt` or `package.json` here. The lab
talks to a public test server (`httpbin.org`) whose authentication endpoints
accept **any** credentials, so **no API key and no account are required**.
The lab needs network access to reach that server; with no network, the
scripts and tests degrade gracefully (they announce the skip and exit 0).
starter/auth_demo.sh (2897 bytes)
#!/usr/bin/env bash
# Day 025 lab — YOUR working file.
# Complete the 4 numbered exercises below. Each names the exact curl command
# to run. httpbin.org accepts ANY credentials, so no real key is needed —
# every value here is an obviously fake placeholder.
#
# Run it with: bash starter/auth_demo.sh
# When you are done, none of the "NOT DONE YET" placeholder lines should remain.
set -uo pipefail
API="https://httpbin.org"
CURL=(curl -s --max-time 25 --retry 5 --retry-delay 2)
echo "=== API Authentication Demo (starter) ==="
# Skip the live requests when offline so the script never hangs.
if ! curl -s -o /dev/null --max-time 12 "${API}/status/200" 2>/dev/null; then
echo "No network access — connect to the internet and re-run to complete the exercises."
exit 0
fi
# ---------------------------------------------------------------------------
# Exercise 1: Basic auth with CORRECT credentials — expect HTTP 200.
# Replace the placeholder echo below with a command that prints just the code:
# "${CURL[@]}" -o /dev/null -w '%{http_code}\n' -u user:pass \
# "${API}/basic-auth/user/pass"
echo "--- Exercise 1: Basic auth, correct credentials (expect 200) ---"
echo "NOT DONE YET — run the curl -u user:pass command described above"
# ---------------------------------------------------------------------------
# Exercise 2: Basic auth with WRONG credentials — expect HTTP 401.
# Replace the placeholder echo below with the same command but a wrong password:
# "${CURL[@]}" -o /dev/null -w '%{http_code}\n' -u user:wrongpass \
# "${API}/basic-auth/user/pass"
echo "--- Exercise 2: Basic auth, wrong credentials (expect 401) ---"
echo "NOT DONE YET — run the curl -u user:wrongpass command described above"
# ---------------------------------------------------------------------------
# Exercise 3: Send a BEARER TOKEN in the Authorization header.
# Replace the placeholder echo below with:
# "${CURL[@]}" -H "Authorization: Bearer token-example-123" "${API}/bearer"
# The endpoint should return JSON with "authenticated": true and your token.
echo "--- Exercise 3: Bearer token in the Authorization header ---"
echo "NOT DONE YET — run the curl -H 'Authorization: Bearer ...' command described above"
# ---------------------------------------------------------------------------
# Exercise 4: Read a bearer token from an ENVIRONMENT VARIABLE (the safe pattern).
# Set a fake token in a variable, then reference the variable so the secret
# never appears inline. Replace the placeholder echo below with these two lines:
# export DEMO_TOKEN="token-example-123"
# "${CURL[@]}" -H "Authorization: Bearer ${DEMO_TOKEN}" "${API}/bearer"
echo "--- Exercise 4: Bearer token from an environment variable ---"
echo "NOT DONE YET — export DEMO_TOKEN and run curl with \$DEMO_TOKEN as described above"
echo "=== End of demo ==="
starter/auth-worksheet.md (2444 bytes)
# Authentication worksheet — Day 025
Fill this in from a real run of the commands (via `bash examples/auth_demo.sh`
or by running each `curl` yourself). Every credential here is fake and
httpbin.org accepts any of them, so you need no account and no real key.
## 1. Basic auth status codes
Run both and record the exact HTTP status code:
```bash
curl -s -o /dev/null -w '%{http_code}\n' -u user:pass https://httpbin.org/basic-auth/user/pass
curl -s -o /dev/null -w '%{http_code}\n' -u user:wrongpass https://httpbin.org/basic-auth/user/pass
```
- Status code for CORRECT credentials (`user:pass`): __________
- Status code for WRONG credentials (`user:wrongpass`): __________
- What does each code mean, in one sentence?
- 200: ______________________________________________________
- 401: ______________________________________________________
## 2. Bearer token
```bash
curl -s -H "Authorization: Bearer token-example-123" https://httpbin.org/bearer
```
- Did the endpoint accept your token (did the JSON say `"authenticated": true`)? __________
- What token did it echo back? __________
- Which HTTP header carried the token, and in what exact format? __________
## 3. Key in a header
```bash
curl -s -H "X-API-Key: demo-key" https://httpbin.org/headers
```
- Did your `X-API-Key` header appear in the server's echo of the headers it saw? __________
- Why is sending a key in a header safer than sending it in the URL query string?
Name two places a query-string key can leak:
1. ______________________________________________________
2. ______________________________________________________
## 4. Storing a real key safely (the important part)
Imagine you will call a paid API tomorrow. Write 4–6 sentences answering:
- What environment variable name would you read the key from?
- Where would the `.env` file live, and why must it be in `.gitignore`?
- Why should you never hard-code the key in your source?
- What would you do the *instant* you discovered the key had been committed to a
public repository — and why is deleting the commit not enough?
Your answer:
_________________________________________________________________
_________________________________________________________________
_________________________________________________________________
_________________________________________________________________
_________________________________________________________________
tests/run_tests.sh (4613 bytes)
#!/usr/bin/env bash
# Tests for the Day 025 lab. Run from the lab directory:
# bash tests/run_tests.sh
#
# Two kinds of checks:
# * Structure checks always run and must pass (files present, the example
# script exercises each scheme, the starter names its exercises, both
# scripts parse, and no realistic long key-shaped string is present).
# * Network checks run only when the internet is reachable. Offline, they
# are SKIPPED with a message. When online but the public test server
# (httpbin.org) is transiently returning 5xx, the affected check is also
# SKIPPED rather than failed — an external outage is not the learner's bug.
#
# The script exits 0 when no structure check failed, whether online or offline.
set -u
lab_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
API="https://httpbin.org"
failures=0
checks=0
skips=0
pass() { checks=$((checks + 1)); echo " ok: $1"; }
fail() { checks=$((checks + 1)); failures=$((failures + 1)); echo " FAIL: $1"; }
skip() { skips=$((skips + 1)); echo " skip: $1"; }
echo "== Structure checks =="
example="${lab_dir}/examples/auth_demo.sh"
starter="${lab_dir}/starter/auth_demo.sh"
[ -f "${example}" ] && pass "example script exists" || fail "example script missing"
[ -f "${starter}" ] && pass "starter script exists" || fail "starter script missing"
[ -f "${lab_dir}/starter/auth-worksheet.md" ] && pass "worksheet exists" || fail "worksheet missing"
# The example must exercise each scheme.
for needle in "user:pass" "user:wrongpass" "Authorization: Bearer" "X-API-Key" "DEMO_TOKEN"; do
if grep -q -- "${needle}" "${example}"; then pass "example uses '${needle}'"; else fail "example missing '${needle}'"; fi
done
# The starter must name its four numbered exercises.
ex_count="$(grep -cE 'Exercise [1-4]:' "${starter}" 2>/dev/null || true)"
if [ "${ex_count:-0}" -ge 4 ]; then pass "starter has 4 numbered exercises"; else fail "starter has 4 numbered exercises (found ${ex_count:-0})"; fi
# Both scripts must be valid bash.
if bash -n "${example}" 2>/dev/null; then pass "example has valid bash syntax"; else fail "example has a syntax error"; fi
if bash -n "${starter}" 2>/dev/null; then pass "starter has valid bash syntax"; else fail "starter has a syntax error"; fi
# Safety: no realistic long key-shaped string should appear anywhere in the lab.
# (Fake placeholders like token-example-123 are short and hyphenated; a real
# key is a long unbroken run of letters/digits. Flag any 32+ char run.)
if grep -rInE '[A-Za-z0-9]{32,}' "${lab_dir}" >/dev/null 2>&1; then
fail "a long key-shaped string is present — use only short fake placeholders"
else
pass "no realistic long key-shaped strings present"
fi
echo
echo "== Network checks =="
if ! curl -s -o /dev/null --max-time 12 "${API}/status/200" 2>/dev/null; then
skip "no network access — skipping all live-request checks (expected offline)"
else
pass "network reachable (${API} responded)"
# Check 1: correct Basic auth returns 200.
ok="$(curl -o /dev/null -s --max-time 25 --retry 5 --retry-delay 2 \
-w '%{http_code}' -u user:pass "${API}/basic-auth/user/pass" 2>/dev/null)" || ok="000"
case "${ok}" in
200) pass "correct Basic auth returned HTTP 200" ;;
5* | 000) skip "Basic-auth-correct check — httpbin transiently unavailable (got '${ok}'); retry later" ;;
*) fail "correct Basic auth should return 200 (got '${ok}')" ;;
esac
# Check 2: wrong Basic auth returns 401. A 401 is not a transient error, so
# --retry returns it without retrying; a 5xx means the server is overloaded.
bad="$(curl -o /dev/null -s --max-time 25 --retry 5 --retry-delay 2 \
-w '%{http_code}' -u user:wrongpass "${API}/basic-auth/user/pass" 2>/dev/null)" || bad="000"
case "${bad}" in
401) pass "wrong Basic auth returned HTTP 401" ;;
5* | 000) skip "Basic-auth-wrong check — httpbin transiently unavailable (got '${bad}'); retry later" ;;
*) fail "wrong Basic auth should return 401 (got '${bad}')" ;;
esac
# Check 3: the bearer endpoint accepts a token and returns 200.
br="$(curl -o /dev/null -s --max-time 25 --retry 5 --retry-delay 2 \
-w '%{http_code}' -H "Authorization: Bearer token-example-123" "${API}/bearer" 2>/dev/null)" || br="000"
case "${br}" in
200) pass "bearer endpoint accepted the token (HTTP 200)" ;;
5* | 000) skip "bearer check — httpbin transiently unavailable (got '${br}'); retry later" ;;
*) fail "bearer endpoint should return 200 (got '${br}')" ;;
esac
fi
echo
echo "${checks} checks, ${failures} failure(s), ${skips} skip(s)."
[ "${failures}" -eq 0 ]
Troubleshooting
Troubleshooting — Day 025 lab
Every request prints 000
000 from curl -w '%{http_code}' means no HTTP response arrived — you are
offline, or a proxy/firewall is blocking the request. Check connectivity with
curl -s -o /dev/null -w '%{http_code}\n' https://httpbin.org/status/200. The
scripts and tests detect this and skip the live requests, exiting 0.
A request prints 503
httpbin.org is a shared free service and is occasionally overloaded,
returning 503 Service Temporarily Unavailable. That is the server, not your
credential. Wait a minute and retry; the scripts already pass
--retry 5 --retry-delay 2 to ride past brief outages, and the test script
treats a transient 5xx as a SKIP rather than a failure.
Basic auth prints 401 even with user:pass
The path and the credentials must agree: /basic-auth/user/pass expects
username user and password pass, exactly what -u user:pass sends. Check
for a typo, and make sure you did not change one without the other.
$DEMO_TOKEN comes through empty
You either opened a new shell after export, or mistyped the variable name.
Run the export DEMO_TOKEN=... line and the curl line in the same shell,
and confirm with echo "$DEMO_TOKEN" before sending.
curl: command not found
Install curl: macOS ships it; on Debian/Ubuntu run sudo apt install curl, on
Fedora sudo dnf install curl. Verify with curl --version.
Permission denied when running a script
Run it through bash explicitly: bash starter/auth_demo.sh. You do not need to
mark it executable; if you prefer ./starter/auth_demo.sh, first run
chmod +x starter/auth_demo.sh.
The bearer endpoint returned 401 instead of 200
httpbin's /bearer endpoint requires the header format to be exactly
Authorization: Bearer <token> with a non-empty token. Check the header
spelling and that a token follows Bearer (with one space).
Windows: bash is not recognized
Use WSL (wsl --install, then open Ubuntu) or Git Bash, and run the Linux
commands unchanged. The scripts are plain curl and behave identically there.
Security notes
Security notes — Day 025 lab
CRITICAL: this lab uses only fake credentials against a public test server. Nothing here is a real secret, and nothing here should ever be a real secret.
- Only fake credentials. Every value the scripts send —
user:pass,token-example-123,demo-key,token-from-env-example— is an obviously fake placeholder. httpbin.org's auth endpoints accept any credentials, so the lab proves how each scheme works without any real key existing. - Never put a real API key in a script. A key pasted into source code is
one accidental
git pushaway from the entire internet. Read real keys from an environment variable at runtime; the literal string must never appear in a file you might commit, share, or screenshot. - Never commit a
.envfile. Store real keys in a.envfile and add that file to.gitignoreso git never tracks it. Commit only a.env.examplethat lists the variable names with empty values. - Rotate leaked keys immediately. The instant a real key appears in any public place — a repository, a log, a chat, a screenshot — treat it as burned: revoke it at the provider and issue a new one. Deleting the commit is not enough, because automated scanners find committed keys within minutes and the key may already be copied elsewhere.
- Least privilege. When a provider lets you scope a key (read-only, one project, a spend cap), create the narrowest key that does the job so a leak has the smallest possible blast radius.
- Always use TLS. The lab only ever contacts
https://URLs. A credential sent over plain HTTP — including Basic auth, whose base64 encoding hides nothing — is exposed to anyone on the network path. - What the scripts do: send HTTP requests with
curltohttpbin.organd print the responses. They write no files, need no elevated privileges, and contain no real secret. Read them before running — a habit this course reinforces for every script.