Using Service Principal Authentication With FabricRestClient

Sandeep PawarSandeep Pawar
3 min read

I wrote a blog on using FabricRestClient from Semantic Link to call Power/Fabric REST API in Fabric notebooks. It makes it super easy. However, as I noted in the blog, it uses the calling user’s identify. It didn’t support SPN. Well, that was then and now you can use SPN to generate auth token.

All I had to do was see how Semantic Link Lab does it. I looked at the source code (Labs is open source) and copy it. So, all credit goes to Michael Kovalsky and Semantic Link Labs contributors, I am just copying what they did.

  • Create a service principal. Make sure you give it the permissions as required by whichever API you are calling and the API supports SPN (check the API documentation for that)

  • Create client secret

  • While optional, I very highly recommend using Azure Key Vault (hopefully one day this will be possible natively in Fabric) to store the SPN credentials. Use notebookutils to retrieve the creds from AKV.

Below, I create a class to get the token which is used in FabricRestClient .

from sempy.fabric import FabricRestClient
from azure.identity import ClientSecretCredential

key_vault = "https://sempykeyvaultsandeep.vault.azure.net/"
tenant_id = notebookutils.credentials.getSecret(key_vault , "tenantid") 
client_id = notebookutils.credentials.getSecret(key_vault , "clientid") 
client_secret = notebookutils.credentials.getSecret(key_vault , "secret") 

class ServicePrincipalTokenProvider:
    def __init__(self, tenant_id, client_id, client_secret):
        self.credential = ClientSecretCredential(
            tenant_id=tenant_id,
            client_id=client_id,
            client_secret=client_secret
        )
        # The scope for the API
        self.scope = "https://analysis.windows.net/powerbi/api/.default"

    def __call__(self):
        token = self.credential.get_token(self.scope)
        return token.token

# init service principal credentials
token_provider = ServicePrincipalTokenProvider(
    tenant_id=tenant_id,
    client_id=client_id,
    client_secret=client_secret
)

# Create the Fabric client
client = FabricRestClient(token_provider=token_provider)

# call the required API, below is using admin api to get tennat settings
# change the api url as required
response = client.get("/v1/admin/tenantsettings")
response.json()

If you have used the .admin functions in Labs, you know that it uses the context managers to call the API, i.e. you use with to localize the call. You don’t have to but that’s the recommended way to ensure isolation, security and performance. With the context manager, you use the SPN token only during that particular call and use your own identify for all other calls. Let’s implement that too:

from sempy.fabric import FabricRestClient
from azure.identity import ClientSecretCredential
import contextlib

class ServicePrincipalAuth: #base class
    def __init__(self, tenant_id, client_id, client_secret):
        self.tenant_id = tenant_id
        self.client_id = client_id
        self.client_secret = client_secret
        self.client = None

    def __enter__(self):
        token_provider = self._create_token_provider()
        self.client = FabricRestClient(token_provider=token_provider)
        return self.client

    def __exit__(self, exc_type, exc_val, exc_tb):

        self.client = None

    def _create_token_provider(self):
        # create a creds
        credential = ClientSecretCredential(
            tenant_id=self.tenant_id,
            client_id=self.client_id,
            client_secret=self.client_secret
        )

        def token_provider(): #replace the provder url if required
            token = credential.get_token("https://analysis.windows.net/powerbi/api/.default")
            return token.token

        return token_provider

# use it in context manager
with ServicePrincipalAuth(tenant_id, client_id, client_secret) as client:
    # Use the client inside the context - replace with the api url
    #always check the api documentation for requirements and details
    response = client.get("/v1/admin/workspaces") #get all workspaces as an admin
    # Process the response
    workspacess = response.json()

What are the use cases here? Well, when you call APIs directly or indirectly using Semantic Link and Semantic Link Labs, you use your own identify. But with SPN you can execute notebooks using your identify but call APIs using the SPN as required.

💡
I would highly encourage using the functions available in Semantic Link and Semantic Link Lab wherever possible because those are optimized, handle LRO requests, pagination etc. Above is a very basic implementation. Look at SLL’s source code for details.

Thanks again to Semantic Link Labs !

Resources

1
Subscribe to my newsletter

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

Written by

Sandeep Pawar
Sandeep Pawar

Microsoft MVP with expertise in data analytics, data science and generative AI using Microsoft data platform.