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
# 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

  1. Get a GitHub personal access token, use it to star a repo via API

  2. Implement a login flow that saves cookies and reuses them

  3. Create a gopass entry for an API and build a shell function

  4. Decode a JWT and check its expiration time

  5. Build an OAuth client credentials flow for any API that supports it