Azure Blob Storage fails to authenticate (using msal-browser). Required token scope seems to be missing

33 views Asked by At

I'm attempting to put together a tiny (preact, but I don't think that's relevant) app that can retrieve some information from Azure Blob Storage. So far, I can successfully log in using the @azure/msal-browser, which (theoretically) means that my app registration in Azure is at least somewhat correct. Now, I'm attempting to access Azure Blob Storage using @azure/storage-blob's BlobServiceClient.

I have deduced that my app registration needs the API permission user_impersonation for Azure Storage, and I have configured that. I have also added the scope https://storage.azure.com/user_impersonation to both my login request as well as my acquireTokenSilent() request.

The loginPopup() call looks like this (in a "Login" component):

import { useMsal } from "@azure/msal-react";

export default function Login(props) {
    const { instance, accounts, inProgress } = useMsal();

    const doLogin = () => {
        instance.loginPopup({
            scopes: ["user.read", "https://storage.azure.com/user_impersonation"],
        })
    };

    if (accounts.length > 0) {
        return <button onClick={() => instance.logoutPopup()}>logout</button>
    } else if (inProgress == "login") {
        return <span>Login is currently in progress...</span>
    } else {
        return <button onClick={doLogin}>Log In</button>
    }
}

I have created a CustomTokenCredential class as described here (which gives an example of this sort of thing using Vue), that simply takes the access token retrieved by acquireTokenSilent. That seems to satisfy the BlobServiceClient, but the request ultimately fails with a 401 (Server failed to authenticate the request. Please refer to the information in the www-authenticate header.)

The call to acquireTokenSilent() looks like this:

useEffect(() => {
    if (!account) {
        return;
    }

    instance.acquireTokenSilent({
        scopes: ["user.read", "https://storage.azure.com/user_impersonation"],
        account: account,
    }).then(async response => {
        console.log(`acquireTokenSilent got a response`);
        if (response) {
            for (const scope of response.scopes) {
                console.log(`scope: ${scope}`);
            }

            const tokenCredential = new CustomTokenCredential(response.accessToken, response.expiresOn);
            const bsc = new BlobServiceClient("https://myblobcontainername.blob.core.windows.net", tokenCredential);
            const containers: ContainerItem[] = [];

            for await (const container of bsc.listContainers()) {
                containers.push(container);
            }

            setMyData(containers);
        }
    });
}, [account, instance]);

The scopes that get logged are as follows:

  • scope: openid
  • scope: profile
  • scope: User.Read
  • scope: email

I have noticed that the acquireTokenSilent response doesn't include the https://storage.azure.com/user_impersonation scope, and I suspect that this is the reason for the failed request. I am definitely passing in that scope in the call (as well as in the call to loginPopup(); I do not understand why it wouldn't be coming back. What am I doing wrong?

0

There are 0 answers