Service Principal in Microsoft Fabric

In a recent update the Microsoft team introduced service principal support for Fabric APIs.

This means is that now its possible to access Fabric resources using service principals. In one of my previous blog posts, I discussed how to access Fabric resources through REST APIs which you can check out here.

The key advantage of using service principals is that unlike user accounts they are not linked to any individual account reducing the risk of credentials being compromised.

In this article, we'll walk through how to set up a service principal in your Fabric tenant and use it to authenticate access to Fabric resources.

The Setup

To get started ensure you are the Admin of your fabric tenant. Next, under the tenant Settings» Admin Portal

you will have to enable Service principals can use Fabric API’s setting under Tenant settings » Developer settings

After that’s done, you need to register the application at https://entra.microsoft.com

Once logged in, navigate to Applications >>App registrations >> New registration

and register an application

I have registered the application under the name Fabric API Service Principle. We would required Client ID and Tenant ID values to reference in the code from the registered app.

Also Client Secrets will be required.

Now that the feature is enabled in the next step we would grant access to the registered application to connect to the Workspaces. In this case we would assign contributor access to the workspace.

In your Fabric tenant, under the workspace, select Manage access and assign Contributor access to the registered application that was created in the previous step.

The Code

Lets make a simple call to fetch details of a given workspace.

Create a new Console application and declare a bunch of variables.

 private static string responsename = "";
 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 = "My_Workspace"; ## To retrieve details of the workspace
 private static string lakeHouse = "LakeHouse_1"; ## To retrieve the table list of the lakehouse
 private static MyService service = new MyService();
 private static ClientSecretCredential credential;

Create a class MyService that invokes Get method

 public class MyService
 {
     private static readonly HttpClient client = new HttpClient();
     public HttpClient Client => client;
     public async Task<string> GetAsync(string url, AccessToken Token)
     {
         Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token.Token);
         HttpResponseMessage response = await client.GetAsync(url);
         response.EnsureSuccessStatusCode();
         return await response.Content.ReadAsStringAsync();
     }
 }

Create a method that returns the WorkSpaceId based on the value set in the WorkspaceName variable.

  static string GetWorkSpaceId(string responsename)
  {

      JObject jsonObject = JObject.Parse(responsename);
      int i = 0; string workspaceId = "";
      for (i = 0; i < jsonObject["value"].Count(); i++)
      {
          if (jsonObject["value"][i]["displayName"].ToString() == workspaceName)
          {
              workspaceId = jsonObject["value"][0]["id"].ToString();
          }
      }
      return workspaceId;
  }

Create a method to return a Response and set its AccessToken .

  static async Task ReturnResponse(string baseUrl)
  {
      credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
      AccessToken token = await credential.GetTokenAsync(new Azure.Core.TokenRequestContext(scopes));
      responsename = await service.GetAsync(baseUrl, token);

  }

Call to the above method :

 ReturnResponse("https://api.fabric.microsoft.com/v1/workspaces").GetAwaiter().GetResult();
 ReturnResponse($"https://api.fabric.microsoft.com/v1/workspaces/{GetWorkSpaceId(responsename)}").GetAwaiter().GetResult();

 var jsonObject = JsonSerializer.Deserialize<dynamic>(responsename);           
 string prettyJson = JsonSerializer.Serialize(jsonObject, new JsonSerializerOptions { WriteIndented = true });          
 Console.WriteLine(prettyJson);

this will produce the following output

If the access for the service principal is removed from the workspace, the code errors out with an unauthorized error message.

Now that we saw on ways to leverage service principal to make REST API calls, lets check how to get a list of all the tables from a Lakehouse through managed identity.

The following namespace would be required to reference the lakehouse.

using Azure.Storage.Files.DataLake

 string accountUrl = "https://onelake.dfs.fabric.microsoft.com";
 var fileSystemClient = new DataLakeFileSystemClient(new Uri($"{accountUrl}/{workspaceName}"), credential);
 string path = $"{lakeHouse}.Lakehouse/Tables/";
 var paths = fileSystemClient.GetPaths(path, recursive: false);
 foreach (var p in paths)
 {
     Console.WriteLine(p.Name);
 }

The above code print outs the physical paths of the underlying table.

It print outs lk_table_1 as that’s the only table in the lakehouse.

To access OneLake data use the following paths in this piece of code

 string path = $"{lakeHouse}.Lakehouse/Tables/";
  • /<lakehouse name>.Lakehouse/Tables/ for the tables in your Lakehouse

  • /<lakehouse name>.Lakehouse/Files/ for the files in your Lakehouse

  • /<lakehouse name>.Warehouse/Tables/ for the tables in your Warehouse

The complete code :

using Azure.Core;
using Azure.Identity;
using Azure.Storage.Files.DataLake;
using Microsoft.Identity.Client;
using Newtonsoft.Json.Linq;
using System.Net.Http.Headers;
using System.Text.Json;


namespace Fabric_Service_Principal
{
    internal class Program
    {

        private static string responsename = "";
        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 = "Workspace Name"; ## To retrieve details of the workspace
        private static string lakeHouse = "LakeHouse Name"; ## To retrieve the table list of the lakehouse
        private static MyService service = new MyService();
        private static ClientSecretCredential credential;

        static async Task Main(string[] args)
        {

            ReturnResponse("https://api.fabric.microsoft.com/v1/workspaces").GetAwaiter().GetResult();
            ReturnResponse($"https://api.fabric.microsoft.com/v1/workspaces/{GetWorkSpaceId(responsename)}").GetAwaiter().GetResult();

            var jsonObject = JsonSerializer.Deserialize<dynamic>(responsename);
            string prettyJson = JsonSerializer.Serialize(jsonObject, new JsonSerializerOptions { WriteIndented = true });
            Console.WriteLine(prettyJson);


            string accountUrl = "https://onelake.dfs.fabric.microsoft.com";
            var fileSystemClient = new DataLakeFileSystemClient(new Uri($"{accountUrl}/{workspaceName}"), credential);
            string path = $"{lakeHouse}.Lakehouse/Tables/";
            var paths = fileSystemClient.GetPaths(path, recursive: false);
            foreach (var p in paths)
            {
                Console.WriteLine(p.Name );
            }
        }

        static async Task ReturnResponse(string baseUrl)
        {
            credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
            AccessToken token = await credential.GetTokenAsync(new Azure.Core.TokenRequestContext(scopes));
            responsename = await service.GetAsync(baseUrl, token);

        }
        static string GetWorkSpaceId(string responsename)
        {

            JObject jsonObject = JObject.Parse(responsename);
            int i = 0; string workspaceId = "";
            for (i = 0; i < jsonObject["value"].Count(); i++)
            {
                if (jsonObject["value"][i]["displayName"].ToString() == workspaceName)
                {
                    workspaceId = jsonObject["value"][0]["id"].ToString();
                }
            }
            return workspaceId;
        }

        public class MyService
        {

            private static readonly HttpClient client = new HttpClient();
            public HttpClient Client => client;
            public async Task<string> GetAsync(string url, AccessToken Token)
            {
                Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token.Token);
                HttpResponseMessage response = await client.GetAsync(url);
                response.EnsureSuccessStatusCode();
                return await response.Content.ReadAsStringAsync();
            }

        }
    }
}

Conclusion :

To sum up, with the introduction of service principal support for Fabric APIs, it provides a safer and more effective means of gaining access to Fabric resources. Service principals improve overall security and lower the danger of unwanted access by doing away with the requirement for user-specific passwords. You can now quickly set up and verify your service principal in Fabric by following the instructions in this article, which guarantees secure access to Fabric resources.

0
Subscribe to my newsletter

Read articles from Sachin Nandanwar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sachin Nandanwar
Sachin Nandanwar