{
  "schemaVersion": "1.0",
  "code": "7000014",
  "name": null,
  "title": "AADSTS7000014: Device code value isn't valid (fix)",
  "url": "https://brambrinkmeier.com/entra/aadsts/7000014/",
  "alternates": {
    "markdown": "https://brambrinkmeier.com/entra/aadsts/7000014.md",
    "json": "https://brambrinkmeier.com/api/aadsts/7000014.json",
    "html": "https://brambrinkmeier.com/entra/aadsts/7000014/"
  },
  "description": "AADSTS7000014: the device_code in a device code token request isn't valid. Undocumented by Microsoft; reproduced in the lab. Cause, fix, KQL.",
  "summary": "AADSTS7000014 means a device code token request carried a device_code value that Entra doesn't accept. In the lab it came back for a device_code that was never issued. Microsoft doesn't document the code; most likely the app sent a wrong, cut-off or made-up value, not an expired one (that is 70020, per user reports). It has low attack value. Fix how the app stores and sends the code.",
  "inMicrosoftReference": false,
  "unlistedChecked": "2026-10-30",
  "meaningText": "Microsoft's [error code reference](https://learn.microsoft.com/entra/identity-platform/reference-error-codes) doesn't list AADSTS7000014 (checked 30 October 2026). I found no other Microsoft page that documents it either.\n\nMy lab tenant returned this message for a device code token request whose `device_code` value had never been issued:\n\n> AADSTS7000014: The provided value for the input parameter 'device_code' is not valid.\n\nHow the device code flow works, from Microsoft's device code page: the app first calls `/devicecode` and gets back a `device_code`, *\"A long string used to verify the session between the client and the authorization server.\"* The app then polls `/token` with `grant_type=urn:ietf:params:oauth:grant-type:device_code`, the same `client_id` (*\"Must match the `client_id` used in the initial request\"*) and that `device_code`. 7000014 is a rejection of the `device_code` value in that second step.\n\nMicrosoft's page lists the protocol errors an app can get while polling. One of them fits this case:\n\n> `bad_verification_code`: The `device_code` sent to the `/token` endpoint wasn't recognized. Verify that the client is sending the correct `device_code` in the request.\n\nOur reading: 7000014 is the AADSTS code behind an unrecognised `device_code`. The lab run recorded only the message line, not the `error` field, so which protocol error comes with it is not confirmed.\n\nTwo sibling codes have nearly the same wording. Neither is in Microsoft's reference either, and the wording below comes from user reports:\n\n| Code | Message as users posted it | Meaning |\n|---|---|---|\n| 70020 | *\"The provided value for the input parameter 'device_code' is not valid. This device code has expired.\"* ([azure-cli issue 22251](https://github.com/Azure/azure-cli/issues/22251)) | The code was real but its time ran out. |\n| 90132 | *\"The provided value for the input parameter 'device_code' is not valid. Device codes supporting the personal Microsoft Account sign-in audience can only be used for v2 common or consumers tenants.\"* ([Microsoft Q&A 929217](https://learn.microsoft.com/en-us/answers/questions/929217/oauth2-error-aadsts90132-device-code-is-not-valid)) | A personal account was used with a tenant-specific endpoint. |\n\n7000014 carries no such second sentence. It looks like the general \"this value isn't one we recognise\" case.",
  "sections": [
    {
      "heading": "What does AADSTS7000014 mean?",
      "text": "Microsoft's [error code reference](https://learn.microsoft.com/entra/identity-platform/reference-error-codes) doesn't list AADSTS7000014 (checked 30 October 2026). I found no other Microsoft page that documents it either.\n\nMy lab tenant returned this message for a device code token request whose `device_code` value had never been issued:\n\n> AADSTS7000014: The provided value for the input parameter 'device_code' is not valid.\n\nHow the device code flow works, from Microsoft's device code page: the app first calls `/devicecode` and gets back a `device_code`, *\"A long string used to verify the session between the client and the authorization server.\"* The app then polls `/token` with `grant_type=urn:ietf:params:oauth:grant-type:device_code`, the same `client_id` (*\"Must match the `client_id` used in the initial request\"*) and that `device_code`. 7000014 is a rejection of the `device_code` value in that second step.\n\nMicrosoft's page lists the protocol errors an app can get while polling. One of them fits this case:\n\n> `bad_verification_code`: The `device_code` sent to the `/token` endpoint wasn't recognized. Verify that the client is sending the correct `device_code` in the request.\n\nOur reading: 7000014 is the AADSTS code behind an unrecognised `device_code`. The lab run recorded only the message line, not the `error` field, so which protocol error comes with it is not confirmed.\n\nTwo sibling codes have nearly the same wording. Neither is in Microsoft's reference either, and the wording below comes from user reports:\n\n| Code | Message as users posted it | Meaning |\n|---|---|---|\n| 70020 | *\"The provided value for the input parameter 'device_code' is not valid. This device code has expired.\"* ([azure-cli issue 22251](https://github.com/Azure/azure-cli/issues/22251)) | The code was real but its time ran out. |\n| 90132 | *\"The provided value for the input parameter 'device_code' is not valid. Device codes supporting the personal Microsoft Account sign-in audience can only be used for v2 common or consumers tenants.\"* ([Microsoft Q&A 929217](https://learn.microsoft.com/en-us/answers/questions/929217/oauth2-error-aadsts90132-device-code-is-not-valid)) | A personal account was used with a tenant-specific endpoint. |\n\n7000014 carries no such second sentence. It looks like the general \"this value isn't one we recognise\" case."
    },
    {
      "heading": "Reproduced in the lab",
      "text": "On 30 October 2026 an automated lab run sent a device code token request to my lab tenant with a `device_code` that the tenant had never issued. Entra returned `AADSTS7000014` with this first line:\n\n> AADSTS7000014: The provided value for the input parameter 'device_code' is not valid.\n\nThat confirms the code and its wording at the token endpoint. Note what the run expected: 70020, 70000 or 9002313. It got none of those. No row for it appeared in the lab tenant's exported sign-in logs (interactive, non-interactive and service principal, exported 30 October 2026). Likely a device code that was never issued is rejected before any sign-in is recorded, but that is unconfirmed. The KQL on this page returns nothing on the lab rows, as expected."
    },
    {
      "heading": "Is it an attack?",
      "text": "Usually not. It points to an app that sends a broken `device_code`.\n\n| Pattern | Likely meaning |\n|---|---|\n| A CLI, script or tool fails right after it shows the \"enter the code\" message | The app stores or sends the `device_code` wrongly: cut off, double-encoded, or from a different request. Likely, but unconfirmed. |\n| A home-grown device code script that never worked | The script polls with the `user_code` (the short code the user types) instead of the `device_code`. A guess; not tested. |\n| Failures after a long wait | Expect 70020 (expired) rather than 7000014, based on user reports of 70020. |\n| Polling with a different `client_id` or tenant than the `/devicecode` call | Microsoft requires the same `client_id`. Which code this returns is untested. |\n\nDevice code phishing is a real attack: Microsoft's Conditional Access page calls the flow *\"a high-risk authentication method that can be part of a phishing attack.\"* Our reading is that 7000014 is not a sign of it. In that attack the attacker requests a real code and polls with it, so their requests carry a valid `device_code`. A burst of 7000014 from one IP is more likely a broken tool than a phishing kit."
    },
    {
      "heading": "How do I find it in my logs (KQL)?",
      "text": "Microsoft doesn't document which sign-in table 7000014 is logged in. The request likely names a client and a tenant but no user, because no user ever approved a code that was never issued. A row may not exist at all. Check anyway:\n\n```kusto\nunion withsource=SourceTable SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs\n| where TimeGenerated > ago(30d)\n| where ResultType in (\"7000014\", \"70020\", \"90132\")\n| summarize Rows = count(), LastSeen = max(TimeGenerated) by SourceTable, ResultType, AppId\n```\n\nMore useful in practice: see which apps use the device code flow in your tenant and how they fare. `AuthenticationProtocol` lists `deviceCode` among its documented values:\n\n```kusto\nSigninLogs\n| where TimeGenerated > ago(30d)\n| where AuthenticationProtocol == \"deviceCode\"\n| summarize Rows = count(), Users = dcount(UserPrincipalName), Failures = countif(ResultType != \"0\") by AppDisplayName, AppId\n| sort by Rows desc\n```\n\nAn app that shows up here and that you don't expect is worth a look whatever its error code."
    },
    {
      "heading": "How does Conditional Access affect it?",
      "text": "- Microsoft *\"recommends blocking device code flow wherever possible\"* with a Conditional Access authentication flows policy.\n- Our reading: 7000014 is not that block. The value is rejected before any user is known, so there is nothing for a policy to evaluate. A user blocked by such a policy gets a Conditional Access failure on the sign-in, which you investigate on the sign-in's **Conditional Access** tab, as Microsoft's page describes."
    },
    {
      "heading": "How do I fix AADSTS7000014?",
      "text": "**For the user:** start the sign-in again so the app gets a fresh code. If it fails again straight away, report the tool and its version to whoever maintains it.\n\n**For developers:**\n1. Poll `/token` with the exact `device_code` string from the `/devicecode` response. Don't trim, re-encode or truncate it; it is long.\n2. Send the `device_code`, not the `user_code`.\n3. Use the same `client_id` and the same tenant endpoint for both calls.\n4. Handle the documented polling errors: repeat on `authorization_pending` after `interval` seconds; stop on `authorization_declined`, `bad_verification_code` and `expired_token`.\n5. Prefer a library (MSAL) over hand-written polling.\n\n**For admins:**\n1. Find the app with the first query, then ask its owner which tool and version it is.\n2. If nobody should use device code flow, block it with a Conditional Access authentication flows policy. That stops the flow for real attackers too."
    }
  ],
  "microsoftWording": {
    "text": "AADSTS7000014: The provided value for the input parameter 'device_code' is not valid.",
    "source": "Quoted on the page from the lab or from user reports; the code is not in Microsoft's error code reference",
    "sourceUrl": null
  },
  "securityReading": {
    "category": {
      "id": "token",
      "label": "Tokens and sessions"
    },
    "kind": {
      "id": "failure",
      "label": "Failure"
    },
    "attackSignal": {
      "level": "low",
      "label": "Low: usually configuration or user error"
    },
    "mitre": [],
    "tables": [],
    "isItAnAttack": "Usually not. It points to an app that sends a broken `device_code`.\n\n| Pattern | Likely meaning |\n|---|---|\n| A CLI, script or tool fails right after it shows the \"enter the code\" message | The app stores or sends the `device_code` wrongly: cut off, double-encoded, or from a different request. Likely, but unconfirmed. |\n| A home-grown device code script that never worked | The script polls with the `user_code` (the short code the user types) instead of the `device_code`. A guess; not tested. |\n| Failures after a long wait | Expect 70020 (expired) rather than 7000014, based on user reports of 70020. |\n| Polling with a different `client_id` or tenant than the `/devicecode` call | Microsoft requires the same `client_id`. Which code this returns is untested. |\n\nDevice code phishing is a real attack: Microsoft's Conditional Access page calls the flow *\"a high-risk authentication method that can be part of a phishing attack.\"* Our reading is that 7000014 is not a sign of it. In that attack the attacker requests a real code and polls with it, so their requests carry a valid `device_code`. A burst of 7000014 from one IP is more likely a broken tool than a phishing kit.",
    "conditionalAccess": {
      "heading": "How does Conditional Access affect it?",
      "text": "- Microsoft *\"recommends blocking device code flow wherever possible\"* with a Conditional Access authentication flows policy.\n- Our reading: 7000014 is not that block. The value is rejected before any user is known, so there is nothing for a policy to evaluate. A user blocked by such a policy gets a Conditional Access failure on the sign-in, which you investigate on the sign-in's **Conditional Access** tab, as Microsoft's page describes."
    }
  },
  "kql": [
    {
      "title": null,
      "leadIn": "Microsoft doesn't document which sign-in table 7000014 is logged in. The request likely names a client and a tenant but no user, because no user ever approved a code that was never issued. A row may not exist at all. Check anyway:",
      "query": "union withsource=SourceTable SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs\n| where TimeGenerated > ago(30d)\n| where ResultType in (\"7000014\", \"70020\", \"90132\")\n| summarize Rows = count(), LastSeen = max(TimeGenerated) by SourceTable, ResultType, AppId"
    },
    {
      "title": null,
      "leadIn": "More useful in practice: see which apps use the device code flow in your tenant and how they fare. `AuthenticationProtocol` lists `deviceCode` among its documented values:",
      "query": "SigninLogs\n| where TimeGenerated > ago(30d)\n| where AuthenticationProtocol == \"deviceCode\"\n| summarize Rows = count(), Users = dcount(UserPrincipalName), Failures = countif(ResultType != \"0\") by AppDisplayName, AppId\n| sort by Rows desc"
    }
  ],
  "kqlSectionText": "Microsoft doesn't document which sign-in table 7000014 is logged in. The request likely names a client and a tenant but no user, because no user ever approved a code that was never issued. A row may not exist at all. Check anyway:\n\n```kusto\nunion withsource=SourceTable SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs\n| where TimeGenerated > ago(30d)\n| where ResultType in (\"7000014\", \"70020\", \"90132\")\n| summarize Rows = count(), LastSeen = max(TimeGenerated) by SourceTable, ResultType, AppId\n```\n\nMore useful in practice: see which apps use the device code flow in your tenant and how they fare. `AuthenticationProtocol` lists `deviceCode` among its documented values:\n\n```kusto\nSigninLogs\n| where TimeGenerated > ago(30d)\n| where AuthenticationProtocol == \"deviceCode\"\n| summarize Rows = count(), Users = dcount(UserPrincipalName), Failures = countif(ResultType != \"0\") by AppDisplayName, AppId\n| sort by Rows desc\n```\n\nAn app that shows up here and that you don't expect is worth a look whatever its error code.",
  "fix": {
    "short": "User: start the sign-in again so the app gets a fresh code; if it fails again, report the tool and version to its maintainer. Developer: poll `/token` with the exact `device_code` string from `/devicecode` (not the `user_code`), using the same `client_id` and tenant endpoint for both calls.",
    "steps": "**For the user:** start the sign-in again so the app gets a fresh code. If it fails again straight away, report the tool and its version to whoever maintains it.\n\n**For developers:**\n1. Poll `/token` with the exact `device_code` string from the `/devicecode` response. Don't trim, re-encode or truncate it; it is long.\n2. Send the `device_code`, not the `user_code`.\n3. Use the same `client_id` and the same tenant endpoint for both calls.\n4. Handle the documented polling errors: repeat on `authorization_pending` after `interval` seconds; stop on `authorization_declined`, `bad_verification_code` and `expired_token`.\n5. Prefer a library (MSAL) over hand-written polling.\n\n**For admins:**\n1. Find the app with the first query, then ask its owner which tool and version it is.\n2. If nobody should use device code flow, block it with a Conditional Access authentication flows policy. That stops the flow for real attackers too."
  },
  "labReproduced": {
    "date": "2026-10-30",
    "method": "Device code token request against a lab tenant",
    "scenario": "Device code that was never issued",
    "message": "AADSTS7000014: The provided value for the input parameter 'device_code' is not valid.",
    "logCaptured": null,
    "kqlTested": null
  },
  "labSectionText": "On 30 October 2026 an automated lab run sent a device code token request to my lab tenant with a `device_code` that the tenant had never issued. Entra returned `AADSTS7000014` with this first line:\n\n> AADSTS7000014: The provided value for the input parameter 'device_code' is not valid.\n\nThat confirms the code and its wording at the token endpoint. Note what the run expected: 70020, 70000 or 9002313. It got none of those. No row for it appeared in the lab tenant's exported sign-in logs (interactive, non-interactive and service principal, exported 30 October 2026). Likely a device code that was never issued is rejected before any sign-in is recorded, but that is unconfirmed. The KQL on this page returns nothing on the lab rows, as expected.",
  "labRuns": [
    {
      "date": "2026-10-30",
      "run": "round 4",
      "method": "token-endpoint requests against a lab tenant",
      "issue": "https://github.com/GrantZero604/Brambrinkmeier.com/issues/67",
      "scenario": "Device code that was never issued",
      "expected": "70020 / 70000 / 9002313",
      "got": "7000014",
      "matchedExpectation": false,
      "message": "AADSTS7000014: The provided value for the input parameter 'device_code' is not valid."
    },
    {
      "date": "2026-10-30",
      "run": "round 5",
      "method": "token-endpoint requests against a lab tenant, using lab-created test apps",
      "issue": "https://github.com/GrantZero604/Brambrinkmeier.com/issues/69",
      "scenario": "Device code that was never issued",
      "expected": "70020 / 70000 / 9002313",
      "got": "7000014",
      "matchedExpectation": false,
      "message": "AADSTS7000014: The provided value for the input parameter 'device_code' is not valid."
    },
    {
      "date": "2026-10-30",
      "run": "round 5 (first attempt)",
      "method": "token-endpoint requests against a lab tenant",
      "issue": "https://github.com/GrantZero604/Brambrinkmeier.com/issues/68",
      "scenario": "Device code that was never issued",
      "expected": "70020 / 70000 / 9002313",
      "got": "7000014",
      "matchedExpectation": false,
      "message": "AADSTS7000014: The provided value for the input parameter 'device_code' is not valid."
    },
    {
      "date": "2026-10-30",
      "run": "round 6",
      "method": "token-endpoint requests against a lab tenant, using lab-created test apps",
      "issue": "https://github.com/GrantZero604/Brambrinkmeier.com/issues/70",
      "scenario": "Device code that was never issued",
      "expected": "70020 / 70000 / 9002313",
      "got": "7000014",
      "matchedExpectation": false,
      "message": "AADSTS7000014: The provided value for the input parameter 'device_code' is not valid."
    },
    {
      "date": "2026-10-30",
      "run": "round 7",
      "method": "token-endpoint requests against a lab tenant",
      "issue": "https://github.com/GrantZero604/Brambrinkmeier.com/issues/71",
      "scenario": "Device code that was never issued",
      "expected": "70020 / 70000 / 9002313",
      "got": "7000014",
      "matchedExpectation": false,
      "message": "AADSTS7000014: The provided value for the input parameter 'device_code' is not valid."
    },
    {
      "date": "2026-10-30",
      "run": null,
      "method": "ROPC and other token-endpoint requests against a lab tenant",
      "issue": "https://github.com/GrantZero604/Brambrinkmeier.com/issues/66",
      "scenario": "Device code that was never issued",
      "expected": "70020 / 70000 / 9002313",
      "got": "7000014",
      "matchedExpectation": false,
      "message": "AADSTS7000014: The provided value for the input parameter 'device_code' is not valid."
    },
    {
      "date": "2026-10-31",
      "run": "round 8",
      "method": "token-endpoint and device-code requests against a lab tenant",
      "issue": "https://github.com/GrantZero604/Brambrinkmeier.com/issues/72",
      "scenario": "Device code that was never issued",
      "expected": "70020 / 70000 / 9002313",
      "got": "7000014",
      "matchedExpectation": false,
      "message": "AADSTS7000014: The provided value for the input parameter 'device_code' is not valid."
    },
    {
      "date": "2026-10-31",
      "run": "round 9",
      "method": "token-endpoint and device-code requests against a lab tenant",
      "issue": "https://github.com/GrantZero604/Brambrinkmeier.com/issues/73",
      "scenario": "Device code that was never issued",
      "expected": "70020 / 70000 / 9002313",
      "got": "7000014",
      "matchedExpectation": false,
      "message": "AADSTS7000014: The provided value for the input parameter 'device_code' is not valid."
    }
  ],
  "verification": "lab-reproduced",
  "related": [
    "9002313",
    "700016",
    "70016",
    "90023"
  ],
  "coveredBy": [],
  "sources": [
    {
      "title": "Microsoft Entra authentication and authorization error codes",
      "url": "https://learn.microsoft.com/entra/identity-platform/reference-error-codes"
    },
    {
      "title": "Microsoft identity platform and the OAuth 2.0 device authorization grant flow",
      "url": "https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-device-code"
    },
    {
      "title": "Conditional Access: Authentication flows",
      "url": "https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-authentication-flows"
    },
    {
      "title": "Azure/azure-cli issue 22251 (AADSTS70020 with az login --use-device-code)",
      "url": "https://github.com/Azure/azure-cli/issues/22251"
    },
    {
      "title": "Microsoft Q&A: OAuth2 Error AADSTS90132: Device Code is not valid",
      "url": "https://learn.microsoft.com/en-us/answers/questions/929217/oauth2-error-aadsts90132-device-code-is-not-valid"
    },
    {
      "title": "SigninLogs table reference (Azure Monitor)",
      "url": "https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/signinlogs"
    }
  ],
  "published": "2026-10-30",
  "lastUpdated": "2026-10-30",
  "lastVerified": null,
  "license": "CC BY 4.0",
  "licenseUrl": "https://creativecommons.org/licenses/by/4.0/",
  "licenseNote": "CC BY 4.0 covers the author's own text and classification fields. Quoted Microsoft error wording belongs to Microsoft, is quoted with its source and can change.",
  "attribution": "Bram Brinkmeier, \"AADSTS7000014: Device code value isn't valid (fix)\", https://brambrinkmeier.com/entra/aadsts/7000014/ (CC BY 4.0)",
  "microsoftErrorLookup": "https://login.microsoftonline.com/error?code=7000014"
}
