Azure Blob Storage is commonly used to store documents, images, reports, backups, and application files. In many applications, users need temporary access to a file without receiving the storage account key or permanent access to the storage container.
A Shared Access Signature (SAS) provides a way to grant limited access to Azure Storage resources. A SAS token can specify which resource a client can access, which operations it can perform, and when the permission starts and expires.
However, a SAS token is a bearer credential. Anyone who obtains a valid token can use the permissions encoded in it until it expires or is otherwise invalidated.
Setting an expiration time is important, but expiration alone does not make a token secure. Applications must also restrict permissions, select the appropriate SAS type, protect token generation, and avoid exposing tokens unnecessarily.
This article explains how to generate SAS tokens for Azure Blob Storage, configure expiry, restrict access, and handle common security mistakes in .NET applications.
What Is an Azure Blob Storage SAS Token?
A SAS token is a query-string credential that grants scoped access to a storage resource. Depending on the SAS type and its configuration, it can restrict access by resource, permissions, validity period, and supported network or protocol conditions.
For example, an application may allow a user to download a PDF report for the next ten minutes without giving the user access to other files in the storage account.
A SAS URL typically contains a blob URL followed by query parameters:
https://storageaccount.blob.core.windows.net/reports/report.pdf
?sv=...
&se=...
&sp=r
&sig=...
The parameters shown here are illustrative. A real SAS URL contains additional fields depending on the SAS type and storage service version.
Common SAS parameters include:
Parameter | Purpose |
|---|
se
| Expiration time |
st
| Start time, when specified |
sp
| Allowed permissions |
sr
| Resource scope for supported SAS types |
sig
| Signature used to validate the SAS |
spr
| Allowed protocols, such as HTTPS |
A SAS token should be treated like a password. Do not place it in public source code, application logs, analytics events, or URLs that are likely to be shared unintentionally.
Choose the Appropriate SAS Type
Azure Storage supports different SAS types. Choosing the correct one determines how access is granted and revoked.
User delegation SAS
A user delegation SAS for Blob Storage is signed using a user delegation key obtained through Microsoft Entra ID authentication.
This approach avoids signing the SAS directly with the storage account key. It is generally the preferred option when an application can use Microsoft Entra ID and the required Azure role assignments.
The identity generating the SAS must have the necessary permissions to request a user delegation key and access the relevant storage resource.
Service SAS
A service SAS grants access to a specific storage service resource, such as a blob or container. It is signed using a storage account key.
A service SAS can be restricted to specific resources, permissions, and validity periods, but its security depends on protecting the account key used to sign it.
Account SAS
An account SAS can grant permissions across supported storage services and resource types within a storage account. Its scope can be broader than a service SAS.
Use an account SAS only when the application genuinely requires that broader access. It is usually unnecessary for an application that needs to provide temporary access to one blob.
For applications using Microsoft Entra ID, a user delegation SAS is often the best starting point for temporary Blob Storage access.
Install the Azure Storage SDK
For a .NET application, install the Azure Blob Storage SDK:
dotnet add package Azure.Storage.Blobs
The following examples use Azure.Storage.Blobs and Azure.Identity.
dotnet add package Azure.Identity
Use the SDK rather than manually constructing the SAS signature. The SDK handles the signing details and helps avoid mistakes in the query parameters.
For local development, authenticate using an appropriate Azure development identity. In production, prefer managed identity or another supported Microsoft Entra ID credential rather than storing long-lived storage account keys in application configuration.
Generate a User Delegation SAS with an Expiration
The following example generates a read-only SAS for one blob using a user delegation key.
using Azure.Identity;
using Azure.Storage.Blobs;
using Azure.Storage.Sas;
public sealed class BlobSasService
{
private readonly BlobServiceClient _blobServiceClient;
public BlobSasService(
BlobServiceClient blobServiceClient)
{
_blobServiceClient = blobServiceClient;
}
public async Task<Uri> CreateReadSasAsync(
string containerName,
string blobName,
CancellationToken cancellationToken)
{
var blobClient = _blobServiceClient
.GetBlobContainerClient(containerName)
.GetBlobClient(blobName);
var startsOn = DateTimeOffset.UtcNow
.AddMinutes(-5);
var expiresOn = DateTimeOffset.UtcNow
.AddMinutes(10);
var delegationKey =
await _blobServiceClient
.GetUserDelegationKeyAsync(
startsOn,
expiresOn,
cancellationToken);
var sasBuilder = new BlobSasBuilder
{
BlobContainerName = containerName,
BlobName = blobName,
Resource = "b",
StartsOn = startsOn,
ExpiresOn = expiresOn,
Protocol = SasProtocol.Https
};
sasBuilder.SetPermissions(
BlobSasPermissions.Read);
var sasUri = blobClient.GenerateUserDelegationSasUri(
sasBuilder,
delegationKey.Value);
return sasUri;
}
}
This example assumes that the BlobServiceClient is configured to authenticate through Microsoft Entra ID and that the application identity has the required permissions.
The generated SAS is scoped to one blob and allows read access only. It uses HTTPS and a ten-minute validity period. The five-minute start-time offset helps account for clock differences between the issuing application and the storage service.
The user delegation key's validity must cover the SAS's validity period. Azure also limits how long a user delegation key can remain valid, so applications should follow the current service constraints when designing longer-lived access.
For a typical service registration, a client can be configured using a storage account URL and DefaultAzureCredential:
using Azure.Identity;
using Azure.Storage.Blobs;
var blobServiceClient = new BlobServiceClient(
new Uri("https://youraccount.blob.core.windows.net"),
new DefaultAzureCredential());
Replace the example account URL with the actual storage account endpoint. In production, configure the account name or endpoint through application configuration rather than hardcoding environment-specific values.
Set the SAS Expiration Correctly
The ExpiresOn property defines when the SAS becomes invalid due to its expiry time.
For example:
var sasBuilder = new BlobSasBuilder
{
BlobContainerName = "reports",
BlobName = "monthly-report.pdf",
Resource = "b",
StartsOn = DateTimeOffset.UtcNow.AddMinutes(-5),
ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(15),
Protocol = SasProtocol.Https
};
The example grants a validity window of approximately 20 minutes, beginning five minutes before the current time and ending 15 minutes after it.
The right expiry depends on the use case:
A short-lived download link may need only a few minutes.
A report download for an authenticated user may need a somewhat longer window.
An upload workflow may need enough time to complete the upload but no more.
Automated integration jobs may require a different lifecycle, combined with stronger controls over token distribution.
Avoid using a very long expiration simply because it is convenient. If a token leaks, a long validity period increases the time during which it can be misused.
Also, expiration does not guarantee immediate termination of a transfer that has already started. It primarily controls whether a request is authorized when the storage service evaluates the SAS.
Restrict Permissions to the Minimum Required
A SAS should grant only the operations that the client needs.
For a read-only download, use read permission:
sasBuilder.SetPermissions(
BlobSasPermissions.Read);
For an upload workflow, the required permissions depend on how the client uploads the blob. Creating a new blob, overwriting an existing blob, and using block-blob operations may require different permissions.
Do not grant broad permissions by default. For example, a download link should not also allow writing or deleting data.
A useful approach is to define separate SAS-generation methods for different workflows, such as downloading a report, uploading an attachment, or accessing a media file. Each method should specify its resource scope, permissions, and expiry explicitly.
Scope the Token to One Blob
A container-level SAS can provide access to multiple blobs in that container, depending on the granted permissions. If the user needs only one file, use a blob-level SAS where possible.
For example, these are different scopes:
// Blob-level SAS
sasBuilder.Resource = "b";
sasBuilder.BlobName = "report.pdf";
// Container-level SAS
sasBuilder.Resource = "c";
A container-level token may be appropriate for a carefully designed bulk-download or upload workflow, but it provides broader access than a token scoped to one blob.
Do not allow a client to request an arbitrary container name or blob name and then generate a SAS without authorization checks. The application should first verify that the authenticated user is allowed to access the requested resource.
Validate Access Before Generating the SAS
The ability to generate a SAS does not prove that the requesting user should receive it.
Suppose a web application exposes an endpoint that accepts a document ID and returns a download URL. The endpoint must check that the authenticated user has permission to access that document before generating the token.
A simplified controller action might look like this:
[HttpGet("{documentId}/download")]
public async Task<IActionResult> GetDownloadUrl(
string documentId,
CancellationToken cancellationToken)
{
var user = await _currentUserService
.GetCurrentUserAsync(cancellationToken);
if (user is null)
{
return Unauthorized();
}
var document = await _documentService
.GetDocumentForUserAsync(
documentId,
user.Id,
cancellationToken);
if (document is null)
{
return NotFound();
}
var sasUri = await _blobSasService.CreateReadSasAsync(
document.ContainerName,
document.BlobName,
cancellationToken);
return Ok(new
{
downloadUrl = sasUri.ToString(),
expiresInSeconds = 600
});
}
This example assumes that the document service enforces access rules for the authenticated user and that the SAS service uses the configured ten-minute expiry.
The user identity and document ownership must come from trusted application logic, not from an unverified user ID supplied in the request.
For sensitive documents, consider whether returning the SAS URL in a JSON response is appropriate for the client. The token can be used by anyone who obtains it until it expires or is otherwise invalidated.
Understand SAS Revocation and Expiration
SAS tokens are bearer credentials, so applications should have a plan for limiting their lifetime and responding to suspected exposure.
User delegation SAS tokens can be affected by the validity of the user delegation key and the permissions of the identity involved. Service SAS tokens signed with an account key can be affected by account-key rotation. A service SAS associated with a stored access policy may also be governed by changes to that policy, where supported.
However, not every SAS can be individually revoked immediately. An ad hoc SAS is not generally managed as a separate server-side record that can simply be deleted.
For this reason, short expirations are an important security control. If a token is exposed, reduce its remaining lifetime where possible and follow the appropriate Azure revocation or key-rotation procedure for the SAS type.
For highly sensitive files, consider serving downloads through an authenticated application endpoint instead of issuing reusable bearer URLs. This provides more direct control over each download request, although it adds application bandwidth and processing costs.
Avoid Logging SAS URLs
A SAS URL contains the token's signature and authorization parameters. Logging the full URL can expose access to the blob.
Avoid writing the complete URL to application logs, analytics events, exception messages, or telemetry.
For example, do not log the URL like this:
logger.LogInformation(
"Generated SAS URL: {SasUrl}",
sasUri);
Instead, log safe metadata:
logger.LogInformation(
"Generated a read-only SAS for document {DocumentId}. " +
"Expiry: {ExpiresOn}",
documentId,
expiresOn);
Ensure that the document identifier itself is safe to log under your organization's data-handling rules. The important point is to record operational details without recording the bearer credential.
Also, avoid placing SAS URLs in public pages, shared screenshots, or long-lived browser storage. Apply appropriate cache-control and referrer policies to pages that handle sensitive download links.
Common Mistakes to Avoid
Several mistakes can weaken SAS security:
Setting a long expiration without a clear business need.
Granting write or delete permissions to a download token.
Generating a SAS for a container when only one blob is required.
Returning a SAS URL without checking the user's access rights.
Using storage account keys when Microsoft Entra ID and a user delegation SAS are suitable.
Trusting client-supplied blob names or container names without authorization checks.
Logging the full SAS URL.
Assuming every SAS can be revoked individually.
Assuming expiry immediately terminates an operation that has already begun.
Treating a SAS URL as safe because it is difficult to guess.
Security depends on the combination of token scope, expiry, permission selection, identity management, and safe handling of the generated URL.
Conclusion
Azure Blob Storage SAS tokens provide a practical way to grant temporary access to files without exposing permanent storage credentials. Their security depends on restricting access to the required resource and operations, choosing a short validity period, and generating tokens only after verifying the caller's authorization.
For modern .NET applications, a user delegation SAS signed with Microsoft Entra ID is often a good choice. Scope the token to the smallest practical resource, allow only the required permissions, require HTTPS, and avoid logging or sharing the token unnecessarily.
Finally, design for token exposure and revocation from the beginning. Expiration reduces the window of risk, but it does not replace authorization checks or careful handling of bearer credentials.
Join the conversation! Your thoughts help the community grow.