Authorize M2M Requests with Auth0
Configure Apache APISIX to validate Auth0 access tokens and authorize machine-to-machine requests by audience and scope.
Applies to:Apache APISIX 3.18.0
Roles:Platform engineerSecurity engineerTime:20 minutesImpact:No restart, no traffic change
Goal
A route that validates Auth0 machine-to-machine access tokens locally with Auth0's JSON Web Key Set (JWKS), enforces their issuer, audience, and read:messages scope, and removes authentication headers before proxying requests upstream.
Before you begin
- You have administrator access to an Auth0 tenant.
- You have an Apache APISIX installation, cURL, and jq. Get APISIX installs APISIX in Docker or Kubernetes.
Set the APISIX variables this guide uses:
export APISIX_ADMIN_URL="http://127.0.0.1:9180"
export GATEWAY_URL="http://127.0.0.1:9080"
read -rsp "Admin API key: " APISIX_ADMIN_KEY && export APISIX_ADMIN_KEY && echo-
The Admin API accepts your key. Check it with:
curl -s -o /dev/null -w '%{http_code}\n' "${APISIX_ADMIN_URL}/apisix/admin/routes" \ -H "X-API-KEY: ${APISIX_ADMIN_KEY}"Expected:
200. -
The data plane is reachable. Check it with:
curl -sI "${GATEWAY_URL}" | grep -i '^server'Expected: a
Server: APISIX/3.18.0header. -
The route ID
auth0-m2mand path/anything/m2m/*are unused. Check both with:curl -s -o /dev/null -w '%{http_code}\n' \ "${APISIX_ADMIN_URL}/apisix/admin/routes/auth0-m2m" \ -H "X-API-KEY: ${APISIX_ADMIN_KEY}" curl -s "${APISIX_ADMIN_URL}/apisix/admin/routes" \ -H "X-API-KEY: ${APISIX_ADMIN_KEY}" | \ jq -r '.list[] | select(.value.uri == "/anything/m2m/*") | .key'Expected:
404from the first command and no output from the second. A200or listed key means a collision; stop and choose a unique route ID and path rather than overwriting or shadowing an existing route.
Steps
Three steps register the protected API, authorize an M2M application, and create the APISIX route.
Step 1: Register the protected API
In the Auth0 Dashboard, select Applications → APIs → Create API, then configure:
- Name:
APISIX Protected API - Identifier:
https://apisix.test - Access Token Profile:
Auth0 - Signing Algorithm:
RS256
After creating the API, add the permission read:messages with the description Read protected messages in its permissions or scopes settings.
Expected: the API overview shows identifier https://apisix.test, signing algorithm RS256, and permission read:messages.
What changed: Auth0 now has a resource-server definition whose identifier becomes the access-token audience and whose permission becomes the required scope.
Step 2: Authorize an M2M application
Select Applications → Applications → Create Application, name it APISIX M2M Client, choose Machine to Machine Applications, and authorize it for APISIX Protected API with only read:messages.
From the application's settings, copy its domain, client ID, and client secret into shell variables:
read -rp "Auth0 tenant domain: " AUTH0_DOMAIN && export AUTH0_DOMAIN
read -rp "Auth0 M2M client ID: " AUTH0_M2M_CLIENT_ID && export AUTH0_M2M_CLIENT_ID
read -rsp "Auth0 M2M client secret: " AUTH0_M2M_CLIENT_SECRET && export AUTH0_M2M_CLIENT_SECRET && echo
export AUTH0_API_AUDIENCE="https://apisix.test"
export AUTH0_DISCOVERY="https://${AUTH0_DOMAIN}/.well-known/openid-configuration"Confirm that the discovery document publishes a JWKS endpoint:
curl -sS "${AUTH0_DISCOVERY}" | jq -er '.jwks_uri'Expected: an HTTPS URL under your Auth0 tenant domain.
What changed: the M2M application can now request tokens for this API with the read:messages scope, and the local shell holds the values needed by APISIX.
Step 3: Create the protected route
Create a route that validates bearer tokens locally, requires the configured audience and scope, and strips authentication data before proxying:
curl -s "${APISIX_ADMIN_URL}/apisix/admin/routes/auth0-m2m" -X PUT \
-H "X-API-KEY: ${APISIX_ADMIN_KEY}" \
-H "Content-Type: application/json" \
--data-binary @- <<EOF
{
"uri": "/anything/m2m/*",
"plugins": {
"openid-connect": {
"client_id": "${AUTH0_API_AUDIENCE}",
"discovery": "${AUTH0_DISCOVERY}",
"bearer_only": true,
"use_jwks": true,
"claim_validator": {
"audience": {
"required": true,
"match_with_client_id": true
}
},
"required_scopes": ["read:messages"],
"set_access_token_header": false,
"set_id_token_header": false,
"set_userinfo_header": false
},
"proxy-rewrite": {
"headers": {
"remove": ["Authorization"]
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {"httpbin.org:80": 1}
}
}
EOF{ "value": { "id": "auth0-m2m", "uri": "/anything/m2m/*", "plugins": { "openid-connect": { "bearer_only": true, "use_jwks": true, … } }, … } }What changed: /anything/m2m/* now requires an Auth0 token for the protected API with the read:messages scope, and bearer credentials are not forwarded upstream.
Validate
Request an access token with the Client Credentials flow:
AUTH0_ACCESS_TOKEN="$(
curl -sS "https://${AUTH0_DOMAIN}/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "client_id=${AUTH0_M2M_CLIENT_ID}" \
--data-urlencode "client_secret=${AUTH0_M2M_CLIENT_SECRET}" \
--data-urlencode "audience=${AUTH0_API_AUDIENCE}" \
--data-urlencode "scope=read:messages" \
--data-urlencode "grant_type=client_credentials" | \
jq -er '.access_token'
)" && export AUTH0_ACCESS_TOKENSend the token to APISIX and verify that httpbin did not receive authentication headers:
curl -sS "${GATEWAY_URL}/anything/m2m/get" \
-H "Authorization: Bearer ${AUTH0_ACCESS_TOKEN}" | \
jq '{authorization_forwarded: (.headers.Authorization != null), access_token_forwarded: (.headers["X-Access-Token"] != null), id_token_forwarded: (.headers["X-Id-Token"] != null), userinfo_forwarded: (.headers["X-Userinfo"] != null)}'{
"authorization_forwarded": false,
"access_token_forwarded": false,
"id_token_forwarded": false,
"userinfo_forwarded": false
}A request without a token is rejected:
curl -s -o /dev/null -w '%{http_code}\n' "${GATEWAY_URL}/anything/m2m/get"401Roll back / Clean up
Delete the APISIX route, then remove the guide-specific M2M application and API in Auth0:
curl -s -X DELETE "${APISIX_ADMIN_URL}/apisix/admin/routes/auth0-m2m" \
-H "X-API-KEY: ${APISIX_ADMIN_KEY}"Expected: the Admin API returns a successful deletion response. The protected path stops matching this route immediately.
In the Auth0 Dashboard, delete APISIX M2M Client under Applications → Applications, then delete APISIX Protected API under Applications → APIs. Do not delete either resource if it was shared with another integration.
Troubleshoot
| Symptom | Cause | Fix |
|---|---|---|
The token request returns 401 or access_denied. | The application credentials are wrong, or the application has no client grant for this API. | Recopy the client ID and secret, then confirm the application's API access grants read:messages for APISIX Protected API. |
APISIX returns 401 for a token Auth0 issued. | The discovery URL is unreachable from APISIX, the JWT signature is invalid, or its audience does not match client_id. | Fetch ${AUTH0_DISCOVERY} from the APISIX network and confirm the token request used ${AUTH0_API_AUDIENCE}. |
APISIX returns 403 for a valid token. | The token does not contain the required read:messages scope. | Grant only the needed permission to the M2M application, request a new token, and retry. |
Related
- Set Up SSO with Auth0 — use Auth0 for browser-based user authentication.
openid-connectplugin overview (opens in Plugin Hub docs) — compare bearer-token validation with the plugin's other OIDC flows.openid-connectplugin reference (opens in Plugin Hub docs) — audience, scope, JWKS, and upstream-header options.- Auth0 Client Credentials flow — Auth0's external token-request reference.