# SAS Tokens in Microsoft Fabric

Last month the Fabric team announced the availability of OneLake Shared Access Signatures (SAS) feature for Fabric. You can read the official announcement [here](https://blog.fabric.microsoft.com/en-gb/blog/onelake-shared-access-signatures-sas-now-available-in-public-preview?ft=All).

With this feature, it is now possible to grant access to Fabric resources for a fixed period but it comes with a few limitations.

* OneLake SAS are always short-lived, with a maximum lifetime of 1 hour.
    
* OneLake SAS are always user-delegated, and must be backed by an Entra Identity.
    
* OneLake SAS only grant access to folders and files within Fabric data items, like lakehouses.
    

Onelake SAS is compatible with Azure Storage tools and SDKs. So it supports both DFS and Blob endpoints and the account name for OneLake is always `onelake`.  
  
In this example I will demonstrate the creation of SAS token through `Azure SDK` and highlight the error/issue I am facing. I will use the `DFS` endpoints to create a SAS token for access to a lakehouse in my Fabric tenant.

### The Setup

I would use the same registered application for Entra ID that I used in one of my earlier [article](https://www.azureguru.net/using-azure-data-lake-service-to-manage-fabric-lakehouse#heading-the-setup).

The next step is to enable three settings on your Fabric tenant

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1728421774435/3024cb21-d8c3-4fc6-844f-07a481b78142.png align="left")

Of the above three settings, setting 2 and 3 are crucial in terms of managing the use of Onelake SAS.

**\*\* Setting 2 (Use short-lived user-delegated SAS tokens)** It manages the generation of user delegation keys. User delegation keys are generated at the tenant-level and are controlled by a tenant setting.

\*\* **Setting 3 (Authenticate with OneLake user-delegated SAS tokens)** is turned off by default and can be turned on by an admin who wants to allow authentication with a OneLake SAS in their workspace. A tenant admin can turn this setting on for all workspaces via the tenant setting, or leave it to workspace admins to turn on.

### The Code

Create a new console application and declare a bunch of variables

```csharp
 private static string clientId = "Client Id of the Registered App";
 private static string tenantId = "Tenant Id of the Registered App";
 private static string clientSecret = "Client Secret of the Registered App";
 private static string workspaceName = "Your Workspace";
 private static string lakeHouse = "Your LakeHouse";
 private static string endpoint = $"https://onelake.dfs.fabric.microsoft.com//{Your Workspace}//{Your LakeHouse}.LakeHouse/Files";
 private static ClientSecretCredential credential;
 private static DataLakeServiceClient datalake_Service_Client;
 private static DataLakeFileSystemClient dataLake_FileSystem_Client;
```

Method to return a `Credential` object for the Entra ID

```csharp
  public static Task<ClientSecretCredential> ReturnCredentials(string baseUrl)
  {
      credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
      return Task.FromResult(credential);
  }
```

Call to the above method

```csharp
  credential = await ReturnCredentials(endpoint);
```

Next, set the `DataServiceClient`

```csharp
  datalake_Service_Client = new DataLakeServiceClient(new Uri(endpoint), credential);
```

Now we create a `userDelegationKey`.

The following method gets a user delegation key for the `DFS` service that is valid for 1 hour.

```csharp
  public static Task<UserDelegationKey> RequestUserDelegationKey(DataLakeServiceClient datalakeServiceClient)
  {
          UserDelegationKey userDelegationKey =
           datalakeServiceClient.GetUserDelegationKey(
              DateTimeOffset.UtcNow,
              DateTimeOffset.UtcNow.AddHours(1));
      return Task.FromResult(userDelegationKey);
  }
```

Call for the above method :

```csharp
 UserDelegationKey userDelegationKey = RequestUserDelegationKey(datalake_Service_Client).Result;
```

Next we define a method that will generate a Shared Access Signature (SAS) URI for the `DFS` resource using the `UserDelegationKey` that we created earlier.

We use the `sasBuilder` object and set the expiry period to one hour as OneLake SAS token by default supports only one hour till its expiry and set the permission level to read/write `rw`.The valid permission settings are `rw`, `rd`, `rl`, `wd`, `wl`, and `rl`.

```csharp
  public static Task<Uri> CreateDfsSasUri(DataLakeServiceClient dfsClient, UserDelegationKey userDelegationKey)
  {
      DataLakeSasBuilder sasBuilder = new DataLakeSasBuilder()
      {
          FileSystemName = dfsClient.AccountName,
          Resource = "c",
          StartsOn = DateTimeOffset.UtcNow,
          ExpiresOn = DateTimeOffset.UtcNow.AddHours(1)
      };

      sasBuilder.SetPermissions(DataLakeAccountSasPermissions.Read | DataLakeAccountSasPermissions.Write);
      string Sas = sasBuilder.ToSasQueryParameters(userDelegationKey, endpoint).ToString();
      Uri sasUri = new Uri($"{dfsClient.Uri}?{Sas}");

      return Task.FromResult(sasUri);
  }
```

The complete code

```csharp
using Azure.Identity;
using Azure.Storage.Files.DataLake;
using Azure.Storage.Sas;
using UserDelegationKey = Azure.Storage.Files.DataLake.Models.UserDelegationKey;

namespace Fabric_SAS
{
    internal class Program
    {
      private static string clientId = "Client Id of the Registered App";
      private static string tenantId = "Tenant Id of the Registered App";
      private static string clientSecret = "Client Secret of the Registered App";
      private static string workspaceName = "Your Workspace";
      private static string lakeHouse = "Your LakeHouse";
      private static string endpoint = $"https://onelake.dfs.fabric.microsoft.com//Your Workspace//Your LakeHouse.LakeHouse/Files";
      private static ClientSecretCredential credential;
      private static DataLakeServiceClient datalake_Service_Client;
      private static DataLakeFileSystemClient dataLake_FileSystem_Client;

        static async Task Main(string[] args)
        {
            credential = await ReturnCredentials(endpoint);
            datalake_Service_Client = new DataLakeServiceClient(new Uri(endpoint), credential);
            UserDelegationKey userDelegationKey = RequestUserDelegationKey(datalake_Service_Client).Result;
            Uri sasUri = await CreateDfsSasUri(datalake_Service_Client, userDelegationKey);
            Console.WriteLine(sasUri);
        }

        public static Task<ClientSecretCredential> ReturnCredentials(string baseUrl)
        {
            credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
            return Task.FromResult(credential);
        }
        public static Task<UserDelegationKey> RequestUserDelegationKey(DataLakeServiceClient datalakeServiceClient)
        {

            UserDelegationKey userDelegationKey =
                 datalakeServiceClient.GetUserDelegationKey(
                    DateTimeOffset.UtcNow,
                    DateTimeOffset.UtcNow.AddHours(1));

            return Task.FromResult(userDelegationKey);
        }

        public static Task<Uri> CreateDfsSasUri(DataLakeServiceClient dfsClient, UserDelegationKey userDelegationKey)
        {
            DataLakeSasBuilder sasBuilder = new DataLakeSasBuilder()
            {
                FileSystemName = dfsClient.AccountName,
                Resource = "c",
                StartsOn = DateTimeOffset.UtcNow,
                ExpiresOn = DateTimeOffset.UtcNow.AddHours(1)
            };

            sasBuilder.SetPermissions(DataLakeAccountSasPermissions.Read | DataLakeAccountSasPermissions.Write);
            string Sas = sasBuilder.ToSasQueryParameters(userDelegationKey, endpoint).ToString();
            Uri sasUri = new Uri($"{dfsClient.Uri}?{Sas}");

            return Task.FromResult(sasUri);
        }

    }
}
```

### The Issue

Unfortunately, it isn't working for me. Without sufficient documentation it's difficult to pinpoint the exact issue. I'm not sure if it's a problem on my end or it is indeed a bug. The error message states that it expects bearer token, but if bearer token is expected then the entire purpose of having SAS token is defeated. With Shared Access Signature (SAS) token typically there is no need to send a bearer token in the request as the SAS token already contains the necessary authorization information such as permissions and the expiration time.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1728427548684/ab6412ac-e1bf-4cae-93f6-68a34c675bfa.gif align="left")

I checked the parameters of the request multiple times and they look fine to me

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1728427516196/71115f16-3998-4d1d-9fe6-c35e5a1a5b36.png align="left")

I have posted the issue in Microsoft fabric forum.

[https://community.fabric.microsoft.com/t5/Data-Engineering/SAS-Token-in-fabric-Not-Working/m-p/4234190#M4501](https://community.fabric.microsoft.com/t5/Data-Engineering/SAS-Token-in-fabric-Not-Working/m-p/4234190#M4501)

Will update the article once I get a fix for the issue.

Thanks for reading !!!
