Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Microsoft Teams By James Joyner IV · · 9 min read

Getting the Right Graph Token Audience with az account get-access-token

Quick answer

Get the correct Microsoft Graph token audience from az account get-access-token for ChannelMessage.Send: use --resource or --scope, decode the JWT aud, fix InvalidAuthenticationToken.

  • #microsoft-teams
  • #microsoft-graph
  • #azure
  • #troubleshooting
Free toolkit

Fixing errors like this? Get 500 free DevOps AI prompts

500 copy-paste AI prompts for the stack you actually run — one PDF, free.

Exact Error Message

{
  "error": {
    "code": "InvalidAuthenticationToken",
    "message": "Access token validation failure. Invalid audience.",
    "innerError": {
      "date": "2026-07-15T09:40:11",
      "request-id": "9f8e7d6c-..."
    }
  }
}

You get this 401 Unauthorized when calling POST /teams/{team-id}/channels/{channel-id}/messages (or any Graph endpoint) with a token from az account get-access-token. The token is real and unexpired — it is simply aimed at the wrong service.

What the Error Means

Every access token has an audience (aud) claim naming the API it is valid for. Microsoft Graph only accepts tokens whose audience is Graph itself. By default, az account get-access-token mints a token for Azure Resource Manager (https://management.azure.com), because that is what most az commands talk to. Hand that ARM token to Graph and Graph says: this token was not issued for meInvalidAuthenticationToken: Invalid audience.

This is an authentication failure (401), distinct from a permission failure (403 Authorization_RequestDenied). Fixing the audience is about which token you request, not what it is allowed to do.

Common Causes

  • Calling az account get-access-token with no --resource/--scope, so you get an ARM-audience token.
  • Passing a resource that is close but wrong, e.g. https://management.core.windows.net/ or a Graph resource with a trailing typo.
  • Using the legacy Azure AD Graph audience (https://graph.windows.net) instead of Microsoft Graph (https://graph.microsoft.com). They are different APIs.
  • Reusing a cached token from an earlier command that targeted a different resource.

Two Correct Ways to Request a Graph Token

Using —resource (audience form)

az account get-access-token \
  --resource https://graph.microsoft.com \
  --query accessToken -o tsv

Here --resource is the audience URI. The resulting token has aud: https://graph.microsoft.com. Note: the resource has no trailing /.default — that suffix belongs to the scope form, not the resource form.

Using —scope (v2 form)

az account get-access-token \
  --scope https://graph.microsoft.com/.default \
  --query accessToken -o tsv

--scope uses the v2.0 endpoint convention, where /.default means “all statically consented scopes for this resource.” Both approaches yield a token Graph accepts; pick one and be consistent. Do not combine --resource and --scope in the same call.

How to Reproduce the Error

Request a token with no resource (defaults to ARM) and call Graph:

TOKEN=$(az account get-access-token --query accessToken -o tsv)

curl -s -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  "https://graph.microsoft.com/v1.0/teams/{team-id}/channels/{channel-id}/messages" \
  -d '{"body":{"content":"test"}}'
{ "error": { "code": "InvalidAuthenticationToken", "message": "Access token validation failure. Invalid audience." } }

Diagnostic Commands: Decode the JWT

The fastest way to diagnose an audience problem is to decode the token’s payload. A JWT is three base64url segments separated by dots; the middle one holds the claims:

echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq '{aud, scp, roles, appid, upn}'

A correct Graph delegated token shows:

{
  "aud": "https://graph.microsoft.com",
  "scp": "ChannelMessage.Send User.Read ...",
  "upn": "you@contoso.com"
}

A wrong (ARM) token shows something like:

{
  "aud": "https://management.azure.com/",
  "scp": "user_impersonation"
}

If aud is not exactly https://graph.microsoft.com, that is your bug. Note that base64url may need padding; if base64 -d errors, this variant is more forgiving:

python3 -c "import sys,base64,json; p=sys.argv[1].split('.')[1]; p+='='*(-len(p)%4); print(json.dumps(json.loads(base64.urlsafe_b64decode(p)),indent=2))" "$TOKEN"

Step-by-Step Resolution

  1. Re-request the token with the Graph audience explicitly set:
TOKEN=$(az account get-access-token \
  --resource https://graph.microsoft.com \
  --query accessToken -o tsv)
  1. Verify the aud claim before doing anything else:
echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq -r '.aud'
# expect: https://graph.microsoft.com
  1. Confirm you are on Microsoft Graph, not Azure AD Graph. If aud is https://graph.windows.net, you targeted the deprecated API — switch the resource to https://graph.microsoft.com.

  2. Retry the Graph call. With the audience correct, a valid delegated call returns 201 Created:

curl -s -o /dev/null -w "%{http_code}\n" -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  "https://graph.microsoft.com/v1.0/teams/{team-id}/channels/{channel-id}/messages" \
  -d '{"body":{"content":"audience fixed"}}'
  1. If you now get 403 instead of 401, the audience is right and you have moved on to a permissions problem — check that the token is delegated (has scp, not roles) and that ChannelMessage.Send is consented.

Prevention and Best Practices

  • Never call az account get-access-token for Graph without --resource https://graph.microsoft.com or --scope https://graph.microsoft.com/.default.
  • Add a one-line aud assertion to scripts so a wrong-audience token fails loudly instead of returning a confusing 401.
  • Remember the distinction: 401 InvalidAuthenticationToken = wrong/expired token or audience; 403 Authorization_RequestDenied = right token, insufficient permission.
  • Do not mix Microsoft Graph and the deprecated Azure AD Graph resources; only https://graph.microsoft.com works for Teams messaging.
  • For scripting these token checks and Graph calls, the DevOps AI prompt library includes ready-to-use Azure CLI and Graph prompts.
  • 403 Authorization_RequestDenied — correct audience but the identity lacks permission (often an app-only token used for ChannelMessage.Send).
  • CompactToken parsing failed — the token is truncated or malformed, not merely the wrong audience.
  • 401 / Lifetime validation failed — the token has expired; request a fresh one.
  • AADSTS500011 — the resource principal was not found in the tenant, usually a wrong resource URI.

Frequently Asked Questions

Why does my token work for az but not for Graph? By default az account get-access-token targets Azure Resource Manager, so its aud is https://management.azure.com. Graph only accepts tokens with aud: https://graph.microsoft.com, so you must pass --resource or --scope.

What is the difference between —resource and —scope? --resource https://graph.microsoft.com sets the audience directly (v1.0 style). --scope https://graph.microsoft.com/.default uses the v2.0 scope convention. Both yield a Graph-audience token; use one, not both.

How do I check the audience of my token? Base64-decode the second segment of the JWT and read the aud claim, for example echo "$TOKEN" | cut -d. -f2 | base64 -d | jq .aud. It must be https://graph.microsoft.com.

Does the resource need a trailing /.default? Only with --scope. With --resource you pass the bare audience https://graph.microsoft.com and no /.default suffix.

I fixed the audience and now get 403 instead of 401 — why? That is progress: authentication now succeeds, and you have a permissions issue. Ensure the token is delegated and that ChannelMessage.Send is consented. For more, see the Microsoft Teams guides.

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.