Un notebook Fabric qui appelle une API a besoin de deux choses : un secret (stocké dans Azure Key Vault) et un jeton d’accès. Les deux passent par notebookutils.credentials. Ce billet teste ses fonctions une par une.

Vérifié en lab en septembre 2026, dans un notebook PySpark, avec un compte qui a le rôle Key Vault Secrets Officer sur le coffre.

1. Ce que la bibliothèque annonce

Premier réflexe : demander à Fabric ce qui existe.

notebookutils.credentials.help()

Sortie de notebookutils.credentials.help() : getToken, getSecret et getSecretWithConnection

Trois fonctions annoncées :

FonctionRôle
getToken(audience)Obtenir un jeton d’accès pour un service
getSecret(akvVaultUri, secret)Lire un secret Key Vault, avec l’identité de l’utilisateur
getSecretWithConnection(connectionId, secret)Lire un secret via une connexion Fabric

2. getSecret : l’URL, pas le nom

kv_uri  = "https://kv-techniir-lab-weu-001.vault.azure.net/"
kv_name = "kv-techniir-lab-weu-001"

user = notebookutils.credentials.getSecret(kv_uri, "sqldb-adventureworks-admin-user")   # fonctionne
user2 = notebookutils.credentials.getSecret(kv_name, "sqldb-adventureworks-admin-user") # échoue

getSecret : l’URL complète fonctionne, le nom seul du coffre est refusé avec Invalid vault uri

  • Avec l’URL complète, le secret est lu. print affiche [REDACTED] : Fabric masque la valeur dans la sortie.
  • Avec le nom seul du coffre : AkvHttpClientException: Invalid vault uri.

Attention au [REDACTED] : c’est un filet de sécurité, pas une protection. La variable contient bien la valeur en clair. Ne jamais afficher un secret.

3. putSecret : absent de l’aide, mais présent, et refusé

help() ne liste pas putSecret. La fonction existe pourtant : l’appel ne renvoie pas fonction inconnue, il part vers le service et revient en erreur.

notebookutils.credentials.putSecret(kv_uri, "test-value-putsecret", "valeur-de-test")

putSecret : TokenServiceClientResponseStatusException, Token Service returned ‘Client Error’ (403)

Résultat : 403, renvoyé par le Token Service, alors que le compte a le rôle Secrets Officer, qui autorise l’écriture. D’après le message, le refus ne vient donc pas des droits Key Vault.

À retenir : un notebook lit les secrets, il ne les écrit pas. L’écriture se fait côté Azure : portail, CLI, ou infrastructure as code.

4. getToken : appeler une API avec son propre jeton

import requests

token = notebookutils.credentials.getToken("pbi")
print("Longueur du jeton :", len(token))

resp = requests.get(
    "https://api.powerbi.com/v1.0/myorg/groups",
    headers={"Authorization": f"Bearer {token}"}
)
print("Code HTTP :", resp.status_code)
for ws in resp.json().get("value", [])[:5]:
    print(ws["name"])

getToken(“pbi”) puis appel de l’API groups : code 200 et liste des workspaces de l’utilisateur

Une ligne suffit pour obtenir un jeton, et l’API répond 200 avec la liste des workspaces. Deux bonnes pratiques au passage : on affiche la longueur du jeton, jamais le jeton lui-même ; et ce jeton est celui de l’utilisateur : l’API renvoie tous les workspaces auxquels il a accès.

5. Service principal : secrets dans Key Vault, jeton d’application

Pour un traitement automatisé, on ne veut pas dépendre du compte d’une personne. On utilise un service principal, dont les identifiants sont rangés dans Key Vault :

import requests

kv_uri    = "https://kv-techniir-lab-bicep.vault.azure.net/"
tenant_id = notebookutils.credentials.getSecret(kv_uri, "entra-tenant-id")
client_id = notebookutils.credentials.getSecret(kv_uri, "sp-fabric-app-id")

resp = requests.post(
    f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token",
    data={
        "grant_type":    "client_credentials",
        "client_id":     client_id,
        "client_secret": notebookutils.credentials.getSecret(kv_uri, "sp-fabric-app-secret"),
        "scope":         "https://analysis.windows.net/powerbi/api/.default",
    },
)
app_token = resp.json().get("access_token", "")
print("Jeton d'application obtenu :", len(app_token) > 0)

groups = requests.get(
    "https://api.powerbi.com/v1.0/myorg/groups",
    headers={"Authorization": f"Bearer {app_token}"}
)
print(groups.status_code, [g["name"] for g in groups.json().get("value", [])])

Jeton d’application du service principal puis appel de l’API groups : code 200, seulement deux workspaces

Même API, résultat différent : le service principal ne voit que deux workspaces (ws_dp700_sandbox et ws_dp700_sandbox_test), là où l’utilisateur en voyait davantage. Le jeton d’application ne donne accès qu’aux workspaces où le service principal a été ajouté. C’est exactement ce qu’on veut : le traitement n’a que les droits dont il a besoin.

Le secret du service principal n’est jamais stocké dans une variable affichée : il est lu dans Key Vault et passé directement à la requête.

À retenir

  • getSecret attend l’URL complète du coffre, pas son nom.
  • [REDACTED] n’est pas une protection : ne jamais afficher un secret ni un jeton.
  • putSecret n’est pas dans l’aide et échoue en 403 : un notebook lit les secrets, il ne les écrit pas.
  • getToken donne un jeton utilisateur : il voit tout ce que l’utilisateur voit.
  • Un service principal dont les identifiants sont dans Key Vault ne voit que les workspaces où il a été ajouté.

À lire aussi : Key Vault en mode RBAC : le piège du premier secret pour créer le coffre et attribuer les rôles.