hashicorp/terraform · error
unable to build authorizer for Storage API: %+v
Error message
unable to build authorizer for Storage API: %+v
What it means
Thrown by Azure buildClient in the use_azuread_authentication=true branch when auth.NewAuthorizerFromCredentials cannot construct an Azure AD authorizer for the Storage data-plane scope. The %+v expands the underlying auth error (missing field, bad endpoint, wrong tenant, etc.).
Solutions
- Read the wrapped %+v error first; it usually names the exact missing/invalid field.
- Confirm client_id, tenant_id (or tenant_id for OIDC), and client_secret / client_certificate_path / use_oidc are all set and consistent in the azurerm backend block or ARM_* env vars.
- Verify the environment name resolves a Storage endpoint (for custom clouds, define environment metadata with a Storage resource manager).
- Rotate an expired client secret and update both the app registration and the backend config.
Example fix
# before: missing tenant_id / wrong scope export ARM_CLIENT_ID=00000000-0000-0000-0000-000000000000 export ARM_USE_AAD=true # after export ARM_CLIENT_ID=00000000-0000-0000-0000-000000000000 export ARM_CLIENT_SECRET=... export ARM_TENANT_ID=11111111-1111-1111-1111-111111111111 export ARM_SUBSCRIPTION_ID=22222222-2222-2222-2222-222222222222 export ARM_USE_AAD=true
Defensive patterns
Strategy: validation
Validate before calling
func validateAADCreds(c BackendConfig) error {
if c.AuthConfig == nil { return fmt.Errorf("missing auth config") }
if c.AuthConfig.ClientID == "" { return fmt.Errorf("client_id missing for AAD auth") }
if c.AuthConfig.TenantID == "" { return fmt.Errorf("tenant_id missing for AAD auth") }
if !c.AuthConfig.UseOIDC && c.AuthConfig.ClientSecret == "" && c.AuthConfig.ClientCertificatePath == "" {
return fmt.Errorf("client_secret or client_certificate_path required for non-OIDC AAD auth")
}
return nil
} Type guard
null
Try / catch
client, err := azure.NewClient(ctx, cfg)
if err != nil && strings.Contains(err.Error(), "unable to build authorizer for Storage API") {
// surface the wrapped %+v cause and point the user at client_id/tenant_id/secret
} Prevention
- Keep the SP app registration's client_id, tenant_id, and secret in sync across config and CI.
- Rotate expiring client secrets before they lapse and update both the registration and the backend config.
- For custom clouds, verify the environment metadata defines a Storage endpoint before using AAD auth.
When it happens
Trigger: config.UseAzureADAuthentication is true and auth.NewAuthorizerFromCredentials(ctx, *config.AuthConfig, config.AuthConfig.Environment.Storage) returns err. Typical root causes: client_id/tenant_id/client_secret missing or malformed, the named Environment does not define a Storage endpoint, or the credentials do not match a real app registration.
Common situations: Service principal misconfigured in the backend block or via ARM_* env vars (wrong tenant, expired client_secret, swapped client_id and object_id), using a custom cloud name that has no Storage endpoint, or AAD workarounds that omit required fields.
Related errors
- unable to build authorizer for Resource Manager API: %+v
- sasToken cannot be empty
- building Storage Accounts client: %+v
- Error loading credentials
- One of `access_key`, `sas_token`, `use_azuread_auth` and…
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/4dafaafc28c61651.
Report an issue: GitHub.
Appendix: source
Thrown at internal/backend/remote-state/azure/api_client.go:64
environment: config.AuthConfig.Environment,
storageAccountName: config.StorageAccountName,
}
var armAuthRequired bool
switch {
case config.AccessKey != "":
client.accessKey = config.AccessKey
case config.SasToken != "":
sasToken := config.SasToken
if strings.TrimSpace(sasToken) == "" {
return nil, fmt.Errorf("sasToken cannot be empty")
}
client.sasToken = strings.TrimPrefix(sasToken, "?")
case config.UseAzureADAuthentication:
var err error
client.azureAdStorageAuth, err = auth.NewAuthorizerFromCredentials(ctx, *config.AuthConfig, config.AuthConfig.Environment.Storage)
if err != nil {
return nil, fmt.Errorf("unable to build authorizer for Storage API: %+v", err)
}
default:
// AAD authentication (ARM scope) is required only when no auth method is specified, which falls back to listing the access key via ARM API.
armAuthRequired = true
}
// If `config.LookupBlobEndpoint` is true, we need to authenticate with ARM to lookup the blob endpoint
if config.LookupBlobEndpoint {
armAuthRequired = true
}
if armAuthRequired {
resourceManagerAuth, err := auth.NewAuthorizerFromCredentials(ctx, *config.AuthConfig, config.AuthConfig.Environment.ResourceManager)
if err != nil {
return nil, fmt.Errorf("unable to build authorizer for Resource Manager API: %+v", err)
}
// When using Azure CLI to auth, the user can leave the "subscription_id" unspecified. In this case the subscription id is inferred fromView on GitHub (pinned to d32a084675)