Posted in:

Azure Blob Storage provides the concept of “shared access signatures”, which are a great way to grant time-limited access to read from (or write to) a specific blob in your container.

“SAS” vs “SAS Token” vs “SAS URI”?

The terminology is confusing, as “SAS” on its own can be used to refer to the entire “SAS URI” or sometimes the “SAS Token”, or even just the “signature”. Here's my understanding of what the terms are:

Here is an example of a SAS URI. This is a full URI that can be used to access a blob:

https://myaccount.blob.core.windows.net/sascontainer/sasblob.txt?sv=2015-04-05&st=2015-04-29T22%3A18%3A26Z&se=2015-04-30T02%3A23%3A26Z&sr=b&sp=rw&sip=168.1.5.60-168.1.5.70&spr=https&sig=Z%2FRHIX5Xcg0Mq2rqI3OlWTjEg2tYkboXr1P9ZUXDtkk%3D

Here I have highlighted just the SAS Token portion of the SAS URI. This is the query string appended to the blob’s URI:

https://myaccount.blob.core.windows.net/sascontainer/sasblob.txt?sv=2015-04-05&st=2015-04-29T22%3A18%3A26Z&se=2015-04-30T02%3A23%3A26Z&sr=b&sp=rw&sip=168.1.5.60-168.1.5.70&spr=https&sig=Z%2FRHIX5Xcg0Mq2rqI3OlWTjEg2tYkboXr1P9ZUXDtkk%3D

Here I have highlighted just the signature only. This is calculated from the rest of the SAS URI, and requires the Storage Account connection string to calculate. Note that creating this signature is purely in-memory operation – as long as you have the Storage Account connection string, you can generate one without the target blob needing to exist, or needing access to the Storage Account REST API.

https://myaccount.blob.core.windows.net/sascontainer/sasblob.txt?sv=2015-04-05&st=2015-04-29T22%3A18%3A26Z&se=2015-04-30T02%3A23%3A26Z&sr=b&sp=rw&sip=168.1.5.60-168.1.5.70&spr=https&sig=Z%2FRHIX5Xcg0Mq2rqI3OlWTjEg2tYkboXr1P9ZUXDtkk%3D

SAS Usage Guidelines

Shared access signatures can be a great way to share files between microservices in a larger application, as well as making files available to end users for download or display in a webpage. But they can cause problems if used incorrectly. So here are a few guidelines I shared with my development team recently, that might be relevant for your projects too.

  1. Prefer to pass around full SAS URIs. Sometimes I see code that passes just the SAS token, and the name of the file, but this means that the consuming code has to make several assumptions to regenerate the full SAS URI, including the name of the Storage Account. This can also cause problems if you want to use the Azure Storage Emulator whose URI structure is different. By providing a full SAS URI, the receiving code can be completely agnostic about where the file is hosted. For example, if it points at an Amazon S3 Bucket instead, your consuming code won't need to change at all.

  2. Keep the lifetime short, (but not too short). Obviously, from a security perspective, you want to keep SAS lifetimes as short as possible. It does need to be at least long enough to allow the recipient to download the item. But if you put a SAS URI in a queue message, then the lifetime of the SAS should not be less than the TTL (time to live) of the queue message. Otherwise, if the message sits in a queue for a couple of days before being read, it will be useless by the time it is consumed.

  3. Avoid long-lived SAS tokens. It can be tempting to create very long-lived SAS tokens if you want to share an item long-term, but this is generally a bad idea. First, the obvious issue is that there is a greater window of time when it could be accessed if it falls into the wrong hands. But second, SAS tokens become invalidated whenever you cycle your Storage Account keys, so the recipient of the SAS token will need a way to refresh it anyway if they really do need long-term access to the file. Which brings us to...

  4. Generate SAS tokens on-demand wherever possible. Generally, rather than creating a long-lived SAS token and storing it in a database, it's better to have an on-demand process for generating a SAS token at the point the blob access is needed. This allows you to keep the durations short.

  5. Don't bother retrying access denied errors. If you're writing code that attempts to use a SAS URI, and you get access denied, consider it a fatal error - it more than likely means it has expired. (BTW, when I create a SAS token I usually set the start time to five minutes ago, to minimize the chance that system clock inaccuracy results in a SAS token that isn't valid yet)

  6. Avoid issuing container-level SAS tokens. Obviously this one depends on what else might be in the container, but generally it's better to use the principle of "least privilege", and generate a SAS token only for the specific blobs that are required. I'd like to see a SAS feature in the future where you could grant access with a wildcard to all blobs matching a prefix, but I don't think that is currently possible. (If you do need to create a container level SAS token, you I created a tutorial here)

  7. Never write SAS tokens into log messages. Log messages are seen by support staff, who should not have access to private customer data. I like to strip off the SAS token portion of the URI before writing it to the logs, allowing support to see which file had a problem, but not the contents of the file.