Read the error code first
The HTTP status is always 403. The x-ms-error-code header or the XML body tells you the real reason.
| Error code | Meaning | Look at |
|---|---|---|
AuthorizationFailure | Often network: firewall or private endpoint rules blocked the caller | Networking |
AuthorizationPermissionMismatch | Identity authenticated but lacks a data-plane role | RBAC |
AuthenticationFailed | Bad signature, expired SAS, or clock skew | SAS / key / time |
KeyBasedAuthenticationNotPermitted | Shared key and SAS disabled on the account | Use Entra ID auth |
AuthorizationResourceTypeMismatch / AuthorizationServiceMismatch | SAS doesn't cover this resource type or service | SAS srt / ss fields |
Network rules
az storage account show -g rg-data -n stproddata \
--query "{public:publicNetworkAccess, default:networkRuleSet.defaultAction, ips:networkRuleSet.ipRules[].ipAddressOrRange, vnets:networkRuleSet.virtualNetworkRules[].virtualNetworkResourceId}" -o json
defaultAction: Denymeans only the listed IPs, VNets and private endpoints get in. The portal itself counts as your client IP, which is why the portal also shows "This request is not authorized" when you browse containers.- Calls from Azure services may need "Allow trusted Microsoft services" or a resource instance rule.
- With a private endpoint, check that the client resolves the account to a private IP (see the Private Endpoint DNS post).
RBAC: management roles aren't data roles
Owner and Contributor manage the account but don't grant access to read blobs with Entra ID. Assign a data role:
| Need | Role |
|---|---|
| Read blobs | Storage Blob Data Reader |
| Read/write/delete blobs | Storage Blob Data Contributor |
| Set ACLs and ownership (ADLS Gen2) | Storage Blob Data Owner |
| Queue messages | Storage Queue Data Contributor |
az role assignment create --assignee <user-or-identity-object-id> \
--role "Storage Blob Data Contributor" \
--scope "/subscriptions/<sub>/resourceGroups/rg-data/providers/Microsoft.Storage/storageAccounts/stproddata/blobServices/default/containers/raw"
Role assignments can take up to about 10 minutes to apply. With ADLS Gen2, POSIX ACLs on folders also apply when RBAC doesn't grant access.
SAS token checklist
Decode the token's query string:
| Field | Check |
|---|---|
se (expiry) / st (start) | Not expired, and the start isn't in the future. Allow for clock skew: set st 15 minutes in the past. |
sp (permissions) | Includes what the call needs: r, w, l (list), c, d |
sr / srt | The resource type matches (blob vs container, service/container/object) |
sip | The caller's public IP is in the range |
spr | https only? Then the client must use HTTPS. |
sv | A service version the target supports |
Tip
Prefer user delegation SAS (signed with Entra ID credentials) over account-key SAS. You can revoke them by revoking the user's delegation keys instead of rotating the whole account key.
Find the failing calls in logs
StorageBlobLogs
| where TimeGenerated > ago(1h) and StatusCode == 403
| project TimeGenerated, OperationName, StatusText, AuthenticationType, CallerIpAddress, Uri
| order by TimeGenerated desc
AuthenticationType (AccountKey, SAS, OAuth, Anonymous) together with StatusText usually points straight at the cause.