Configure the client
- Create or select an OAuth authentication method for the Customer API integration.
- Use the issuer supplied by Wavac and read its
/.well-known/openid-configurationdocument to findtoken_endpoint. - Request only
customer.discovery.readandcustomer.data.read. The integration policy can further restrict sites and lines. - Use
grant_type=client_credentials. This flow has no interactive user or refresh token.
A client secret is a machine credential. Store it in a secret manager, never in source, Postman collection variables, command history, or logs.
Request and use a token
Both examples discover the token endpoint, request a client-credentials token, and call discovery. Use a secret manager for unattended production clients.
Linux: Bash, curl, and jq
# Set non-secret configuration only for this shell session.
export OIDC_ISSUER='https://your-issuer.example/application/o/customer-api/'
export OIDC_CLIENT_ID='your-client-id'
# Prompt without echoing the client secret or placing it in shell history.
read -rsp 'Client secret: ' OIDC_CLIENT_SECRET
echo
export OIDC_CLIENT_SECRET
# Read the standards-based token endpoint from the issuer metadata.
TOKEN_ENDPOINT=$(curl --silent --show-error --fail \
"${OIDC_ISSUER%/}/.well-known/openid-configuration" \
| jq -r '.token_endpoint')
# Exchange the machine credential for a short-lived access token.
# Supplying curl configuration on stdin keeps the expanded secret out of argv.
ACCESS_TOKEN=$(
curl --silent --show-error --fail --config - <<EOF | jq -r '.access_token'
user = "$OIDC_CLIENT_ID:$OIDC_CLIENT_SECRET"
url = "$TOKEN_ENDPOINT"
request = "POST"
data-urlencode = "grant_type=client_credentials"
data-urlencode = "scope=customer.discovery.read customer.data.read"
EOF
)
# Send the access token in the Authorization header, never in the URL.
curl --fail-with-body \
-H "Authorization: Bearer $ACCESS_TOKEN" \
https://api.wavac.io/api/customer/v1/discovery
# Remove secret values from the shell when the tutorial is complete.
unset ACCESS_TOKEN OIDC_CLIENT_SECRET
Windows: PowerShell
# Set non-secret configuration only for this PowerShell process.
$env:OIDC_ISSUER = 'https://your-issuer.example/application/o/customer-api/'
$env:OIDC_CLIENT_ID = 'your-client-id'
# Prompt without echoing the client secret or placing it in command history.
$secureSecret = Read-Host 'Client secret' -AsSecureString
$clientSecret = [System.Net.NetworkCredential]::new('', $secureSecret).Password
# Read the standards-based token endpoint from the issuer metadata.
$issuer = $env:OIDC_ISSUER.TrimEnd('/')
$metadata = Invoke-RestMethod -Uri "$issuer/.well-known/openid-configuration"
# Encode the client credential for OAuth HTTP Basic authentication.
$credentialBytes = [System.Text.Encoding]::UTF8.GetBytes(
"$($env:OIDC_CLIENT_ID):$clientSecret")
$basicCredential = [Convert]::ToBase64String($credentialBytes)
# Exchange the machine credential for a short-lived access token.
$token = Invoke-RestMethod -Method Post -Uri $metadata.token_endpoint `
-Headers @{ Authorization = "Basic $basicCredential" } `
-ContentType 'application/x-www-form-urlencoded' `
-Body @{
grant_type = 'client_credentials'
scope = 'customer.discovery.read customer.data.read'
}
# Send the access token in the Authorization header, never in the URL.
Invoke-RestMethod -Uri 'https://api.wavac.io/api/customer/v1/discovery' `
-Headers @{ Authorization = "Bearer $($token.access_token)" }
# Remove secret values from memory when the tutorial is complete.
$clientSecret = $null
$basicCredential = $null
$credentialBytes = $null
$token = $null
$secureSecret.Dispose()
Runnable .NET example
The C# client is cross-platform. Set its environment variables using the Linux or Windows instructions below, then place this commented example in Program.cs.
// Import HTTP authentication and JSON helpers from the .NET base class library.
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
// Read configuration from the process environment instead of source code.
var issuer = Required("OIDC_ISSUER").TrimEnd('/');
using var client = new HttpClient();
// Discover the token endpoint advertised by the configured OIDC issuer.
var metadata = await client.GetFromJsonAsync<OidcMetadata>(
$"{issuer}/.well-known/openid-configuration");
// Request only the scopes needed by this machine-to-machine client.
using var tokenRequest = new HttpRequestMessage(HttpMethod.Post, metadata!.TokenEndpoint) {
Content = new FormUrlEncodedContent(new Dictionary<string, string> {
["grant_type"] = "client_credentials",
["client_id"] = Required("OIDC_CLIENT_ID"),
["client_secret"] = Required("OIDC_CLIENT_SECRET"),
["scope"] = "customer.discovery.read customer.data.read"
})
};
// Reject an unsuccessful token response before attempting to read a token.
var tokenResponse = await client.SendAsync(tokenRequest);
tokenResponse.EnsureSuccessStatusCode();
var token = await tokenResponse.Content.ReadFromJsonAsync<TokenResponse>();
// Attach the short-lived token to the API request and call discovery first.
client.DefaultRequestHeaders.Authorization = new("Bearer", token!.AccessToken);
Console.WriteLine(await client.GetStringAsync(
"https://api.wavac.io/api/customer/v1/discovery"));
// Fail clearly when required process configuration is missing.
static string Required(string name) => Environment.GetEnvironmentVariable(name)
?? throw new InvalidOperationException($"Set {name}.");
// Map only the JSON properties this small tutorial client consumes.
sealed record OidcMetadata([property: JsonPropertyName("token_endpoint")] string TokenEndpoint);
sealed record TokenResponse([property: JsonPropertyName("access_token")] string AccessToken);
Linux: Run with Bash
# Create the console project once and replace Program.cs with the example above.
dotnet new console --name CustomerApiOAuth
cd CustomerApiOAuth
# Set credentials in this shell, then compile and run the tutorial.
export OIDC_ISSUER='https://your-issuer.example/application/o/customer-api/'
export OIDC_CLIENT_ID='your-client-id'
read -rsp 'Client secret: ' OIDC_CLIENT_SECRET && echo
export OIDC_CLIENT_SECRET
dotnet run
Windows: Run with PowerShell
# Create the console project once and replace Program.cs with the example above.
dotnet new console --name CustomerApiOAuth
Set-Location CustomerApiOAuth
# Set non-secret values for this process and securely prompt for the secret.
$env:OIDC_ISSUER = 'https://your-issuer.example/application/o/customer-api/'
$env:OIDC_CLIENT_ID = 'your-client-id'
$secret = Read-Host 'Client secret' -AsSecureString
$env:OIDC_CLIENT_SECRET = [System.Net.NetworkCredential]::new('', $secret).Password
# Compile and run, then clear the process-local plaintext value.
dotnet run
$env:OIDC_CLIENT_SECRET = $null
$secret.Dispose()
Runnable Python example
This client discovers the token endpoint, requests a client-credentials token, and calls discovery using only Python's standard library. Save it as customer_api_oauth.py.
# Import only Python standard-library modules; no package installation is required.
import base64
import json
import os
import urllib.parse
import urllib.request
# Fail clearly when required process configuration is missing.
def required(name: str) -> str:
value = os.environ.get(name)
if not value:
raise RuntimeError(f"Set {name}.")
return value
# Read machine configuration from the process environment instead of source.
issuer = required("OIDC_ISSUER").rstrip("/")
client_id = required("OIDC_CLIENT_ID")
client_secret = required("OIDC_CLIENT_SECRET")
# Discover the token endpoint advertised by the configured OIDC issuer.
with urllib.request.urlopen(
f"{issuer}/.well-known/openid-configuration", timeout=30
) as response:
metadata = json.load(response)
# Encode the client credential for OAuth HTTP Basic authentication.
basic_credential = base64.b64encode(
f"{client_id}:{client_secret}".encode("utf-8")
).decode("ascii")
# Request only the scopes needed by this machine-to-machine client.
token_body = urllib.parse.urlencode(
{
"grant_type": "client_credentials",
"scope": "customer.discovery.read customer.data.read",
}
).encode("utf-8")
token_request = urllib.request.Request(
metadata["token_endpoint"],
data=token_body,
headers={
"Authorization": f"Basic {basic_credential}",
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
},
method="POST",
)
# Exchange the machine credential for a short-lived access token.
with urllib.request.urlopen(token_request, timeout=30) as response:
access_token = json.load(response)["access_token"]
# Send the access token in the Authorization header, never in the URL.
discovery_request = urllib.request.Request(
"https://api.wavac.io/api/customer/v1/discovery",
headers={
"Authorization": f"Bearer {access_token}",
"Accept": "application/json",
},
)
with urllib.request.urlopen(discovery_request, timeout=30) as response:
print(response.read().decode("utf-8"))
Linux: Run Python with Bash
# Set non-secret values for this shell and prompt without echoing the secret.
export OIDC_ISSUER='https://your-issuer.example/application/o/customer-api/'
export OIDC_CLIENT_ID='your-client-id'
read -rsp 'Client secret: ' OIDC_CLIENT_SECRET && echo
export OIDC_CLIENT_SECRET
# Run the client, then remove the secret from this shell.
python3 customer_api_oauth.py
unset OIDC_CLIENT_SECRET
Windows: Run Python with PowerShell
# Set non-secret values for this process and securely prompt for the secret.
$env:OIDC_ISSUER = 'https://your-issuer.example/application/o/customer-api/'
$env:OIDC_CLIENT_ID = 'your-client-id'
$secret = Read-Host 'Client secret' -AsSecureString
$env:OIDC_CLIENT_SECRET = [System.Net.NetworkCredential]::new('', $secret).Password
# Run the client, then clear the process-local plaintext value.
py customer_api_oauth.py
$env:OIDC_CLIENT_SECRET = $null
$secret.Dispose()
Token lifecycle
Cache the access token in memory until shortly before its expiry, then request a new token. On 401, discard the cached token and retry authentication once. Do not repeatedly request tokens for every data page.