User Delegated SAS Token
It is advisable to generate a User Delegated SAS token instead of using the Storage Account token. In order to use user delegated SAS tokens, there are quite a few extra steps involved which will be outlined below. This tutorial will focus on “normal” storage accounts, not hierarchical storage accounts.
The following URL was referenced and contains important information especially regarding hierarchical namespace storage accounts, as you can set permissions on a directory level, not just a container or blob level.
URL | Purpose |
https://learn.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas | Outlines the HMAC and the properties needed for specific API versions |
Step 1: App Registration (Service Principal) and Storage Setup
Just create an app registration and create a secret for it. No need to assign any APIs to it. This App registration will be used to create the user delegated SAS tokens.
Next, create a storage account. Just a normal account, not a hierarchical namepsace account.
Next, create a container inside that storage account. Make sure the container does not allow anonymous access
Step 2: Permissions
When using User Delegated permissions, the User Delegated SAS token permissions must be a subset of the App Registration permissions.
What does this mean
For example, if your App Registration has “Storage Blob Data Reader” access on the storage account, and you generate a User Delegated SAS token with “rw” (read/write), the SAS token will fail because the App Registration does not have write permissions on the strage account.
So if you want your User Delegated SAS tokens to have, for example, “rwdl” (read/write/delete/list) permissions, your App Registration will need “Storage Blob Data Contributor” permissions on either the container, or the entire storage account.
Delegation permissions
The “Storage Blob Data Contributor” permissions include the “Storage Blob Delegator” permission. But, if you, for example, only assign your App Registration “Storage Blob Data Reader” permissions in the storage acount or container, you have to assign it the “Storage Blob Delegator” permissions separately.
This “Storage Blob Delegator” permission allows the App Registration to delegate permissions on the storage account, in other words, it allows you to use this App Registration to create User Delegated SAS Tokens.
Take Note: Assign the Storage Blob Delegator permissions to your App Registration at the storage account level. Then assign, for example, Storage Blob Data Reader permissions at the container level.
In the screenshot below “Storage Blob Delegator” is assigned at the Storage Account level, and “Storage Blob Data Reader” is assigned at the container level.
Step 3: Obtain an access token
Now that we have an App Registration, lets first obtain an Access Token for this app registration. Replace the data in brackets with the actual data.
curl --location 'https://login.microsoftonline.com/<tenant_id>/oauth2/v2.0/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=672eba4a-2481-4980-a65e-3a3ed5090907' \
--data-urlencode 'client_secret=<secret>' \
--data-urlencode 'scope=https://storage.azure.com/.default'
Your response will look like this
Step 4: Get a user delegation key
The output of this request is used to create the SAS token and the HMAC in our SAS token, i.e. the “sig” URL parameter value. (step 5).
The Start and Expiry in this request is for how long you want this user delegation key to be valid for, i.e. for how long can you use it to generate a user delegated SAS token.
curl --location 'https://<storage_account_name>.blob.core.windows.net/?restype=service&comp=userdelegationkey' \
--header 'Authorization: Bearer <token_here>' \
--header 'x-ms-version: 2020-12-06' \
--header 'Content-Type: application/xml' \
--data '<?xml version="1.0" encoding="utf-8"?>
<KeyInfo>
<Start>2024-12-25T18:00:00Z</Start>
<Expiry>2024-12-27T19:21:00Z</Expiry>
</KeyInfo>'
Here is what the response will look like. The <Value> tag is our signing key. We will use all the other values as well when we create the user delegated SAS token and to compute the HMAC.
Step 5: Generate the user delegated SAS token
Replace all the <xxxx> values in the code with the XML response from step 4.
import hmac
import hashlib
import base64
# User delegation key from the response
user_delegation_key_value = '<Value>'
permissions = "rwdl"
signed_start = "2024-12-25T18:00:00Z" #SAS Token start
signed_expiry = "2024-12-27T19:21:00Z" #SAS Token expiry
canonical_resource = "/blob/gofakeme/tester" #Must always start with /blob, then your storage account name, then your container name
signed_object_id = "<SignedOid>"
signed_tenant_id = "<SignedTid>"
signed_key_start = "<SignedStart>"
signed_key_expiry = "<SignedEnd>"
signed_key_service = "<SignedService>"
signed_key_version = "<SignedVersion>"
signed_protocol = "https"
signed_version = "2020-12-06" #The SAS token version
signed_resource = "c" #Will pretty much always be c for container level access, especially via Azure Storage Explorer
# Canonical string (with all necessary fields)
# If a field is not needed, just make it blank with a newline
# The last field should NOT have a newline
canonical_string = (
f"{permissions}\n" # Permissions
f"{signed_start}\n" # Start time
f"{signed_expiry}\n" # Expiry time
f"{canonical_resource}\n" # Decoded canonicalized resource
f"{signed_object_id}\n" # Signed Object ID
f"{signed_tenant_id}\n" # Signed Tenant ID
f"{signed_key_start}\n" # Start time of user delegation key
f"{signed_key_expiry}\n" # Expiry time of user delegation key
f"{signed_key_service}\n" # Signed service
f"{signed_key_version}\n" # Signed key version
"\n" # Signed authorized user object ID (optional, blank here)
"\n" # Signed unauthorized user object ID (optional, blank here)
"\n" # Signed correlation ID (optional, blank here)
"\n" # Signed IP (optional, blank here)
f"{signed_protocol}\n" # Signed protocol
f"{signed_version}\n" # Storage version (sv)
f"{signed_resource}\n" # Signed resource (sr)
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
""
)
# Decode the User Delegation Key (Base64 to bytes)
key = base64.b64decode(user_delegation_key_value)
# Create the signature using HMACSHA256 (signing the canonical string with the key)
signature = hmac.new(key, canonical_string.encode('utf-8'), hashlib.sha256).digest()
# Base64 encode the signature to get the 'sig' value
signature_b64 = base64.b64encode(signature).decode('utf-8')
#Generate the SAS token
sas_token = (
f"https://gofakeme.blob.core.windows.net/tester"
f"?sp={permissions}"
f"&st={signed_start}"
f"&se={signed_expiry}"
f"&skoid={signed_object_id}"
f"&sktid={signed_tenant_id}"
f"&skt={signed_key_start}"
f"&ske={signed_key_expiry}"
f"&sks={signed_key_service}"
f"&skv={signed_key_version}"
f"&spr={signed_protocol}"
f"&sv={signed_version}"
f"&sr={signed_resource}"
f"&sig={signature_b64}"
)
print(sas_token)
The SAS token will look like this:
https://gofakeme.blob.core.windows.net/tester?sp=rwdl&st=2024-12-25T18:00:00Z&se=2024-12-27T19:21:00Z&skoid=33794d55-fd56-4d32-9115-55e71bd6fed0&sktid=e7b460e0-4425-4e16-b3c6-ec3d60c6dd3d&skt=2024-12-25T18:00:00Z&ske=2024-12-27T19:21:00Z&sks=b&skv=2020-12-06&spr=https&sv=2020-12-06&sr=c&sig=<redacted>
Take Note: <redacted> above is the HMAC.
Browse the container using the SAS token
Once you have your SAS token you can install Azure Storage Explorer, right click “Storage Accounts”, select “Blob Container or Directory” and paste the super long SAS token.
If all went well, you will be able to see your data
But what about Blob level SAS tokens?
It is very pretty much the same, only change these lines:
permissions = "rwd" #List wont make sense, so remove it
canonical_resource = "/blob/gofakeme/tester/tester.png" #Add your blob
signed_resource = "b" #Change to b for "blob"
f"https://gofakeme.blob.core.windows.net/tester/tester.png" #Same as the canonical blob name
To see if it worked, just paste the SAS URL into a browser and see if the image/text file etc loads. (provided you have read permissions in your permissions list)
Opening this SAS URL in Azure Storage Explorer will throw an error about the “signed_resource” value not being “c”, which makes sense as it has to “explorer the storage” which it can’t do with just permissions on a blob.