Azure Storage
Volume 4 — Azure Foundations
An Azure Storage account is a single resource that can host several different storage services underneath it. This chapter focuses on Blob Storage, since that's what backs document storage across the Traxs suite — with enough of the surrounding services covered to know when they're the better fit instead.
1. The Storage Services Under One Account
| Service | What it stores | Typical use |
|---|---|---|
| Blob | Unstructured binary data — files, documents, images | Document storage, uploaded attachments, backups |
| Table | NoSQL key-value data | Simple, high-scale structured data without a relational database |
| Queue | Messages, for asynchronous processing between components | Lightweight task queuing |
| File | Fully managed file shares (SMB/NFS) | Legacy apps expecting a traditional network file share |
Traxs's document storage use case is Blob Storage specifically — the others exist as options worth knowing about, not currently in active use.
2. Containers and Blobs
Storage Account: traxsstorage
└── Container: documents
├── invoices/2026/07/inv-4821.pdf
├── leave-attachments/req-42/doctor-note.pdf
└── receipts/exp-118.jpg
- A container is roughly analogous to a top-level folder — it's the access-control boundary and the unit
azcommands operate against. - A blob is an individual file. Blob names can contain
/characters, which makes them look like a nested folder structure in tools like Storage Explorer or the Portal — but blob storage is fundamentally flat; there's no real directory object, just naming convention creating the visual illusion of folders.
az storage container create --account-name traxsstorage --name documents
az storage blob upload --account-name traxsstorage --container-name documents --file invoice.pdf --name "invoices/2026/07/inv-4821.pdf"
az storage blob list --account-name traxsstorage --container-name documents --output table
3. Access Tiers
| Tier | Cost to store | Cost to access | Best for |
|---|---|---|---|
| Hot | Higher | Lower | Frequently accessed data — active documents, current-period invoices |
| Cool | Lower | Higher | Infrequently accessed, retained for at least 30 days — older records still occasionally needed |
| Archive | Lowest | Highest, plus a rehydration delay (hours) before the blob is even readable again | Long-term retention, rarely if ever accessed — compliance archives |
Tiers can be set per-blob, not just per-account, and changed after the fact — meaning a lifecycle policy (Section 6) can automatically age documents from Hot to Cool to Archive as they get older, without any application code change.
4. Authentication Methods
| Method | How it works | Best for |
|---|---|---|
| Connection string | A single string embedding the account name and a full access key | Simple, but the key grants broad access to the entire account — must be treated as a secret and rotated if ever exposed |
| SAS (Shared Access Signature) token | A time-limited, scope-limited token granting specific permissions (read-only, a specific container, an expiration time) without exposing the account's actual key | Sharing controlled, temporary access — e.g. a signed URL letting a user download one specific document without broader account access |
| Azure AD / Managed Identity | The calling application authenticates as itself (its managed identity), granted a specific RBAC role like Storage Blob Data Contributor | The strongest option for application-to-storage access — no secret to leak, no manual rotation, access governed entirely by Azure RBAC (see the Azure RBAC chapter) |
Managed identity access is the pattern to prefer for the API's own access to its storage account — it removes an entire category of "the connection string leaked" or "the key needs rotating" concerns, since there's no static credential involved at all.
# Grant the App Service's managed identity blob access, instead of using a connection string
az role assignment create \
--assignee "<app-service-managed-identity-object-id>" \
--role "Storage Blob Data Contributor" \
--scope "/subscriptions/<sub-id>/resourceGroups/traxs-prod/providers/Microsoft.Storage/storageAccounts/traxsstorage"
5. Generating a SAS Token
az storage blob generate-sas \
--account-name traxsstorage \
--container-name documents \
--name "invoices/2026/07/inv-4821.pdf" \
--permissions r \
--expiry 2026-07-24T00:00:00Z \
--https-only
The resulting token, appended to the blob's URL, grants exactly the permissions and time window specified — nothing more. Always set the shortest expiry that's actually practical for the use case — a SAS token embedded in a link (e.g. emailed to a customer) is only as safe as that link is; anyone who obtains the full URL before it expires has exactly the access the token grants.
6. Lifecycle Management
A lifecycle policy automates moving or deleting blobs based on age, without any application code:
{
"rules": [
{
"name": "ArchiveOldInvoices",
"type": "Lifecycle",
"definition": {
"filters": { "blobTypes": ["blockBlob"], "prefixMatch": ["documents/invoices/"] },
"actions": {
"tierToCool": { "daysAfterModificationGreaterThan": 90 },
"tierToArchive": { "daysAfterModificationGreaterThan": 365 }
}
}
}
]
}
This moves invoices older than 90 days to Cool, and older than a year to Archive, automatically and continuously — the kind of ongoing cost optimization that's easy to forget as a manual process but trivial to set up once as policy.
7. CORS on Storage
If a frontend application needs to upload or fetch blobs directly from the browser (rather than proxying through the API), the storage account itself needs CORS rules configured — separate from any CORS configuration on the API:
az storage cors add \
--account-name traxsstorage \
--services b \
--methods GET PUT \
--origins "https://roundtrips.app" \
--allowed-headers "*" \
--exposed-headers "*" \
--max-age 3600
A CORS error in the browser console when uploading directly to blob storage is specifically about this configuration — it has nothing to do with the API's own CORS settings, since the browser is talking directly to the storage account, not through the API at all.
8. Troubleshooting Playbook
| Symptom | Likely cause | What to check |
|---|---|---|
403 This request is not authorized | Wrong/expired SAS token, missing RBAC role for managed identity access, or account key rotated without updating a stored connection string | Confirm which auth method is in use and check its specific validity — expired SAS, missing role assignment, or stale key |
| Blob "not found" but it should exist | Wrong container name, or the blob name (including its full path-like prefix) doesn't match exactly | az storage blob list to confirm the exact name/path as stored |
| Upload succeeds from Postman/API but fails from the browser directly | CORS not configured on the storage account | az storage cors list --account-name to check current rules against the actual origin making the request |
| Archive-tier blob download fails or hangs | Archive blobs require rehydration before they're readable — not instantly accessible | Trigger rehydration first (az storage blob set-tier back to Hot/Cool), then wait for it to complete before attempting to read |
| SAS token stopped working before expected expiry | Account key was regenerated, invalidating account-key-based SAS tokens signed with the old key | Regenerate and redistribute affected SAS tokens after any key rotation |
| Connection string based access broke after a "routine" key rotation | Rotating a storage account key invalidates every connection string signed with that specific key | Update the stored connection string (e.g. in Key Vault/App Service settings) immediately after any key rotation, or migrate to managed identity to avoid this entirely |
9. Quick Reference
| Category | Command / Concept | Purpose |
|---|---|---|
| Containers | az storage container create | Create a container |
| Blobs | az storage blob upload | Upload a file |
| Blobs | az storage blob list | List blobs in a container |
| Auth | Managed identity + RBAC | Preferred for application-to-storage access, no static secret |
| Auth | SAS token | Time-limited, scope-limited access for sharing |
| Tiers | Hot / Cool / Archive | Balance storage cost vs. access cost vs. retrieval delay |
| Automation | Lifecycle policy | Auto-tier or delete blobs by age, no code required |
| CORS | az storage cors add | Required separately from API CORS, for direct browser access |
Part of the Traxs Engineering Handbook — Volume 4: Azure Foundations. Companion chapters in this volume: Azure CLI, Azure RBAC, Azure Networking, Azure Key Vault, Microsoft Entra ID.