Skip to content

AADSTS7000216: No client credential in a client_credentials request (fix)

Short answer: AADSTS7000216 means a token request used grant_type=client_credentials but carried no client_secret, no client_assertion and no request parameter, so Entra had nothing to authenticate the app with. Microsoft's error reference doesn't list it. It is a developer error with low attack value. Send the app's secret or a signed client assertion.

Fix: Developer: add the credential to the POST body. With a secret, send `client_secret`. With a certificate or federated credential, send `client_assertion` plus `client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer`. Full steps

At a glance
CodeAADSTS7000216
In Microsoft's error referenceNo (checked ). Wording on this page is what Entra returned or what users report.
CategoryApp configuration
TypeFailure
Attack signalLow: usually configuration or user error
Where to lookAADServicePrincipalSignInLogs
Microsoft lookuplogin.microsoftonline.com/error?code=7000216

Microsoft’s error code reference doesn’t list AADSTS7000216 (checked 30 October 2026). We found no other Microsoft page that names it. The wording below is from a lab run:

AADSTS7000216: ‘client_assertion’, ‘client_secret’ or ‘request’ is required for the ‘client_credentials’ grant type.

A client-credentials request is an app signing in as itself, so the only thing proving who it is, is the credential in the POST body. 7000216 means there wasn’t one.

Microsoft’s client credentials flow page gives three shapes of that request, and each needs a credential:

Credential Parameters Microsoft lists
Shared secret client_id, scope, client_secret, grant_type=client_credentials
Certificate client_id, scope, client_assertion_type, client_assertion, grant_type=client_credentials
Federated credential client_assertion from an external identity provider

Microsoft’s own note on the certificate form: “The parameters for the certificate-based request differ in only one way from the shared secret-based request: the client_secret parameter is replaced by the client_assertion_type and client_assertion parameters.” client_assertion_type has one accepted value, urn:ietf:params:oauth:client-assertion-type:jwt-bearer.

The error message also names a third option, request. That parameter is not in any of the three tables on Microsoft’s client credentials page, so we can’t tell you from Microsoft’s documentation what Entra accepts in it. Our reading, from the name and the company it keeps in the message, is that it is a signed request object carrying the parameters as a JWT. Unverified, and not something you need in order to fix this error.

7000218 is the same mistake on a different grant. Microsoft lists it as “The request body must contain the following parameter: ‘client_assertion’ or ‘client_secret’.” In our lab the two separated cleanly:

Request Code
grant_type=client_credentials, no credential 7000216
Password grant (ROPC) by a confidential client, no credential 7000218

So if you are reading a log and want to know which flow failed, 7000216 says the caller was doing client credentials. The rest of the family tells you what happened once a secret was sent: 7000215 means the secret was wrong, 7000222 means it had expired, and 700016 means the app ID isn’t in that tenant at all.

Whether Entra treats the caller as a confidential client matters for 7000218 and is explained in Microsoft’s troubleshooting article for that code: a Web redirect URI makes the app confidential, a Mobile and desktop applications redirect URI makes it public, and with no redirect URI in the request the Allow public client flows setting decides. The client-credentials grant has no public-client variant, so 7000216 doesn’t depend on that setting.

On 30 October 2026 an automated lab run sent a client-credentials token request with client_id, scope and grant_type=client_credentials, and no credential of any kind. Entra returned:

AADSTS7000216: ‘client_assertion’, ‘client_secret’ or ‘request’ is required for the ‘client_credentials’ grant type.

Two rounds of the run produced the same code and the same text, and the run had expected 7000218. In the same run a correct secret on the same app returned a token, a wrong secret returned 7000215, and a password grant with no secret returned 7000218. The message is also complete rather than truncated, which is worth noting because the lab records only the first 160 characters of a message.

No sign-in log row was captured. See how pages are checked.

Usually not. It is a request that was never going to work, so it is most often a developer or deployment mistake: an empty secret variable, a configuration file that didn’t load, or a secret read from a store the process can’t reach.

Pattern Likely meaning
A new job or container fails every run, for one app The secret is missing from the environment or the config file. Nothing was ever sent.
Started right after a deployment, for an app that worked before The secret variable lost its value, or the key vault reference is unresolved.
For one app ID, from an IP you don’t own Someone is probing with your app ID. On its own this reveals nothing to them, but check whether that IP also produced 7000215 or a success.
Across many app IDs from one IP Someone enumerating app IDs. Our reading: 7000216 tells the caller nothing about whether an app exists, since the credential check never runs.

Which sign-in table 7000216 lands in is undocumented. Our reading: an app signing in as itself belongs in AADServicePrincipalSignInLogs, but since the request has no credential the service principal may not be resolved at all and nothing may be written. Check both possibilities:

union withsource=SourceTable SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs
| where TimeGenerated > ago(30d)
| where ResultType in ("7000216", "7000218") or ResultSignature in ("7000216", "7000218")
| summarize Rows = count(), LastSeen = max(TimeGenerated) by SourceTable, ResultType

The filter covers ResultSignature as well, because the two tables document the columns differently: for SigninLogs, ResultType “provides the 5-6 digit error code that’s generated during a sign-in event”, while AADServicePrincipalSignInLogs describes ResultType as “Success or Failure” and ResultSignature as “the error code, if any, for the sign-in operation”.

Which app and which source, so you know what to repair:

AADServicePrincipalSignInLogs
| where TimeGenerated > ago(7d)
| where ResultType == "7000216" or ResultSignature == "7000216"
| summarize Failures = count(),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
CredentialTypes = make_set(ClientCredentialType, 5),
Descriptions = make_set(ResultDescription, 3)
by AppId, ServicePrincipalName, IPAddress
| sort by Failures desc

ClientCredentialType is documented as “the type of client credential used. Examples include client assertion, client secret, etc.” On a request with no credential it is likely empty or absent; that is not documented, so check a row.

Pair it with successes to tell a broken deployment from an unknown caller. An IP that never succeeds for the app is the one to look at:

let knownAppIPs =
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(30d)
| where ResultType == "0" or ResultType =~ "Success"
| distinct AppId, IPAddress;
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(7d)
| where ResultType == "7000216" or ResultSignature == "7000216"
| join kind=leftanti knownAppIPs on AppId, IPAddress
| summarize Attempts = count(), LastSeen = max(TimeGenerated) by AppId, ServicePrincipalName, IPAddress
| sort by Attempts desc

A starting threshold: a handful an hour from your own build agents is a configuration bug; a steady stream from an address you don’t recognise is worth an investigation.

For developers:

  1. Put the credential in the POST body. With a secret: client_secret=<the secret value>, URL-encoded. Microsoft: “The client secret must be URL-encoded before being sent.” Basic authentication in the Authorization header is also supported.
  2. With a certificate or a federated credential: send client_assertion=<the JWT> and client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer.
  3. Check the request really carries what you think. An empty environment variable produces an empty client_secret, which Entra reads as absent. Log the parameter names you send, never the values.
  4. Keep grant_type=client_credentials and a scope that is one resource ending in /.default.
  5. If the next error is 7000215, the credential is now arriving and is wrong, which is progress.

For admins:

  1. Identify the app from the query above and tell its owner which credential is missing.
  2. If the app has no usable credential at all, add one: a certificate is preferable to a secret.
  3. If the failures come from an address you don’t own, nothing leaked here, but treat it as probing of that app ID and check for 7000215 or successes from the same address.

Sources

  1. Microsoft Entra authentication and authorization error codes
  2. Microsoft identity platform and the OAuth 2.0 client credentials flow
  3. Error AADSTS7000218 when a confidential client application authenticates to Microsoft Entra ID
  4. Microsoft identity platform application authentication certificate credentials
  5. AADServicePrincipalSignInLogs table reference (Azure Monitor)