OAuth2 / OIDC
Overview
OAuth2 separates who has the credentials from who makes the API call. Instead of sending a username and password with every request, you exchange credentials for a short-lived access token, then use that token until it expires.
OpenID Connect (OIDC) is a layer on top of OAuth2 that adds identity — it tells you who the user is, not just that they are authorized.
For API automation, two flows matter:
-
Client Credentials — machine-to-machine, no user involved
-
Authorization Code — user grants access to an application
Client Credentials Flow
This is the flow for automation, scripts, and service-to-service communication. No browser, no user interaction.
How It Works
Client Authorization Server
| |
|-- POST /token (client_id + secret) --> |
| |
| <------- access_token (JWT) ---------- |
| |
|-- GET /api (Bearer token) -----------> | Resource Server
| |
-
Your application sends
client_idandclient_secretto the token endpoint. -
The authorization server validates the credentials and returns an access token.
-
You use the token in the
Authorization: Bearerheader for API requests. -
When the token expires, you request a new one.
curl Example: Token Request
# Step 1: Request a token
TOKEN_RESPONSE=$(curl -s -X POST \
https://auth.example.com/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}" \
-d "scope=api.read api.write")
# Step 2: Extract the access token
ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token')
# Step 3: Use the token
curl -s \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Accept: application/json" \
https://api.example.com/v1/resources
Token Response Structure
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "api.read api.write"
}
Key fields:
-
access_token— the Bearer token to use in API calls -
expires_in— seconds until the token is invalid (typically 300-3600) -
scope— what the token is authorized to do
Authorization Code Flow
This flow involves a user granting access through a browser. Used when an application acts on behalf of a user.
How It Works
User Application Auth Server Resource Server
| | | |
|-- Click ---> | | |
| |-- Redirect ------> | |
| | | |
|<------------ Login page ---------| |
|-- Consent -> | | |
| | <-- auth_code ---- | |
| |-- POST /token ---> | |
| | <-- access_token - | |
| |-- GET /api ------> | -------------------> |
-
Application redirects user to authorization server login page.
-
User authenticates and consents.
-
Auth server redirects back with an authorization code.
-
Application exchanges the code for an access token (server-side).
-
Application uses the token for API calls.
This flow is not scriptable — it requires a browser. For CLI tools, use the Device Code flow or Client Credentials instead.
Token Refresh Pattern
Access tokens expire. Rather than re-authenticating, use a refresh token:
# Refresh an expired token
TOKEN_RESPONSE=$(curl -s -X POST \
https://auth.example.com/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=${REFRESH_TOKEN}" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}")
ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token')
REFRESH_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.refresh_token')
Not all providers issue refresh tokens for Client Credentials flow. When they do not, simply request a new token when the current one expires.
Shell Function: Auto-Refresh
# Cache token and expiry, refresh automatically
oauth2_token() {
local now
now=$(date +%s)
if [[ -z "$ACCESS_TOKEN" || "$now" -ge "${TOKEN_EXPIRY:-0}" ]]; then
local response
response=$(curl -s -X POST \
"${OAUTH2_TOKEN_URL}" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}")
ACCESS_TOKEN=$(echo "$response" | jq -r '.access_token')
local expires_in
expires_in=$(echo "$response" | jq -r '.expires_in')
TOKEN_EXPIRY=$((now + expires_in - 30)) # Refresh 30s before expiry
fi
echo "$ACCESS_TOKEN"
}
# Usage
curl -s -H "Authorization: Bearer $(oauth2_token)" \
https://api.example.com/v1/resources
Common Providers
| Provider | Token Endpoint Pattern | Notes |
|---|---|---|
Keycloak |
|
Self-hosted, full OIDC |
Azure AD |
|
Microsoft Graph, Azure APIs |
Auth0 |
|
SaaS identity platform |
GitHub |
|
App installations use JWT |
|
Uses signed JWT for Client Credentials |
dsec Pattern
Store OAuth2 credentials in an encrypted environment file:
dsec edit d000 dev/network
Inside the encrypted file:
OAUTH2_TOKEN_URL=https://auth.example.com/oauth2/token
CLIENT_ID=netapi-automation
CLIENT_SECRET=your-client-secret
OAUTH2_SCOPE=api.read api.write
Load before use:
dsource d000 dev/network
# Token request uses environment variables
curl -s -X POST "${OAUTH2_TOKEN_URL}" \
-d "grant_type=client_credentials" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}"
dsunsource
netapi Pattern
netapi handles the token lifecycle automatically:
# From environment (after dsource)
netapi --auth oauth2 keycloak users list
# Explicit flags
netapi --auth oauth2 \
--token-url https://auth.example.com/oauth2/token \
--client-id netapi-automation \
--client-secret secret \
keycloak users list
netapi will:
-
Request a token from the configured token endpoint
-
Cache the token in memory for the duration of the command
-
Automatically refresh if the token expires mid-operation
-
Never write the token to disk