API Authentication
Authentication Types
| Type | Use Case | Security Level |
|---|---|---|
API Key |
Service-to-service, public APIs |
Low-Medium |
Bearer Token (JWT) |
User sessions, OAuth |
Medium-High |
Basic Auth |
Simple auth, internal APIs |
Low (unless HTTPS) |
Session Cookie |
Browser-based sessions |
Medium |
OAuth 2.0 |
Third-party access |
High |
mTLS |
Machine-to-machine |
Very High |
API Keys
Simplest form. Key in header or query param.
# In header (preferred)
curl -s -H "X-API-Key: your-api-key-here" \
https://api.example.com/data
# In query param (less secure - visible in logs)
curl -s "https://api.example.com/data?api_key=your-key"
# Common header names
-H "X-API-Key: KEY"
-H "Api-Key: KEY"
-H "Authorization: ApiKey KEY"
-H "X-Auth-Token: KEY"
Store in gopass
# Store
gopass edit v3/work/myapi
# Content:
---
api_key: sk_live_abc123xyz
base_url: https://api.myservice.com
# Use
curl -s \
-H "X-API-Key: $(gopass show v3/work/myapi | sed '1,/^---$/d' | yq -r '.api_key')" \
"$(gopass show v3/work/myapi | sed '1,/^---$/d' | yq -r '.base_url')/users"
Bearer Tokens (JWT)
JSON Web Tokens - self-contained, time-limited.
# Get token (login)
TOKEN=$(curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"username": "user", "password": "pass"}' \
https://api.example.com/auth/login | jq -r '.access_token')
# Use token
curl -s -H "Authorization: Bearer $TOKEN" \
https://api.example.com/protected/resource
# Decode JWT (inspect payload)
echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq
JWT Structure
header.payload.signature
# Header: algorithm, type
# Payload: claims (sub, exp, iat, custom)
# Signature: verification
Token Refresh Pattern
# Store both tokens
AUTH=$(curl -s -X POST ... | jq -r '{access: .access_token, refresh: .refresh_token}')
# When access expires, use refresh
NEW_ACCESS=$(curl -s -X POST \
-H "Content-Type: application/json" \
-d "{\"refresh_token\": \"$(echo $AUTH | jq -r '.refresh')\"}" \
https://api.example.com/auth/refresh | jq -r '.access_token')
Basic Authentication
Username:password encoded in base64.
# curl handles encoding
curl -s -u "username:password" https://api.example.com/data
# Explicit header
curl -s -H "Authorization: Basic $(echo -n 'username:password' | base64)" \
https://api.example.com/data
# From gopass
curl -s -u "$(gopass show v3/work/api | sed '1,/^---$/d' | yq -r '"\(.username):\(.password)"')" \
https://api.example.com/data
| Only use Basic Auth over HTTPS. Credentials are merely encoded, not encrypted. |
Session Cookies
Browser-style authentication. Login creates session, cookie maintains it.
# Login and save cookies
curl -s -c cookies.txt \
-X POST \
-d "username=user&password=pass" \
https://app.example.com/login
# Use cookies for subsequent requests
curl -s -b cookies.txt https://app.example.com/dashboard
# Pass specific cookie
curl -s -H "Cookie: session=abc123; csrf=xyz789" \
https://app.example.com/api/data
CSRF Tokens
Many apps require CSRF token with cookies:
# 1. Get page to extract CSRF
CSRF=$(curl -s -c cookies.txt https://app.example.com/login \
| grep -oP 'name="csrf_token" value="\K[^"]+')
# 2. Login with CSRF
curl -s -b cookies.txt -c cookies.txt \
-X POST \
-d "username=user&password=pass&csrf_token=$CSRF" \
https://app.example.com/login
# 3. Use session
curl -s -b cookies.txt https://app.example.com/api/data
OAuth 2.0
Third-party authorization.
Client Credentials Flow (Machine-to-Machine)
# Get access token
TOKEN=$(curl -s -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "scope=read write" \
https://auth.example.com/oauth/token | jq -r '.access_token')
# Use token
curl -s -H "Authorization: Bearer $TOKEN" https://api.example.com/resource
Authorization Code Flow (User Consent)
# 1. Direct user to authorize URL (in browser)
https://auth.example.com/authorize?
client_id=YOUR_CLIENT_ID&
redirect_uri=http://localhost:8080/callback&
response_type=code&
scope=read%20write
# 2. User authorizes, redirects to callback with code
# http://localhost:8080/callback?code=AUTH_CODE
# 3. Exchange code for token
TOKEN=$(curl -s -X POST \
-d "grant_type=authorization_code" \
-d "code=AUTH_CODE" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "redirect_uri=http://localhost:8080/callback" \
https://auth.example.com/oauth/token | jq -r '.access_token')
Secure Credential Management
gopass Integration
# Structure
gopass edit v3/work/myservice
---
api_key: sk_live_xxx
client_id: abc123
client_secret: xyz789
base_url: https://api.myservice.com
# Shell function
myservice() {
local creds
creds=$(gopass show v3/work/myservice | sed '1,/^---$/d')
curl -s \
-H "Authorization: Bearer $(echo "$creds" | yq -r '.api_key')" \
"$(echo "$creds" | yq -r '.base_url')/$1" \
| jq "${2:-.}"
}
Environment Variables
# Load from gopass
export API_KEY=$(gopass show v3/work/myservice | sed '1,/^---$/d' | yq -r '.api_key')
# Use
curl -s -H "Authorization: Bearer $API_KEY" https://...
# .envrc (direnv)
export API_KEY=$(gopass show -o v3/work/myservice api_key)
Debugging Auth Issues
# Check what you're sending
curl -v https://api.example.com/protected 2>&1 | grep -E '^> (Authorization|Cookie|X-API)'
# Common issues:
# 401 - Missing or invalid credentials
# 403 - Valid credentials but no permission
# Token expired - check exp claim in JWT
Decode JWT to Check Expiry
decode_jwt() {
echo "$1" | cut -d. -f2 | base64 -d 2>/dev/null | jq
}
# Check expiry
decode_jwt "$TOKEN" | jq '.exp | todate'
Exercises
-
Get a GitHub personal access token, use it to star a repo via API
-
Implement a login flow that saves cookies and reuses them
-
Create a gopass entry for an API and build a shell function
-
Decode a JWT and check its expiration time
-
Build an OAuth client credentials flow for any API that supports it