Unable to authorize using Azure MS Entra ID authentication

Saurabh Bhandari 0 Reputation points
2024-11-07T14:12:44.7866667+00:00

Hi Azure Support Team,

I am trying to access my Azure Storage account using MS Entra ID (formerly Azure Active Directory) authentication, but I'm encountering not found errors. However, with the same setup, I am able to connect successfully using either a shared key or an SAS token.

I have assigned all recommended permissions to my application according to the documentation on Azure Built-In Roles for Blobs.

Responsible Code:

  public static void main(String[] args) {

    String accountName = "{Name}";
    String tenantId = "{Tenant ID}";
    String clientId = "{Client ID}";
    String client secret = "{Client Secret}";
    TokenCredential credential = new ClientSecretCredentialBuilder().tenantId(tenantId).clientId(clientId).clientSecret(secret)
        .build();

    String endpoint = String.format(Locale.ROOT, "https://%s.blob.core.windows.net", accountName);

    BlobServiceClient storageClient = new BlobServiceClientBuilder().endpoint(endpoint).credential(credential)
        .buildClient();
    storageClient.getAccountInfo();
  }

Error:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Exception in thread "main" com.azure.storage.blob.models.BlobStorageException: Status code 404, "?<?xml version="1.0" encoding="utf-8"?><Error><Code>ResourceNotFound</Code><Message>The specified resource does not exist.
RequestId:2aaf04db-401e-0049-491c-31d00b000000
Time:2024-11-07T13:53:40.7460155Z</Message></Error>"
	at com.azure.storage.blob.implementation.util.ModelHelper.mapToBlobStorageException(ModelHelper.java:483)
	at reactor.core.publisher.Mono.lambda$onErrorMap$30(Mono.java:3797)
	at reactor.core.publisher.Mono.lambda$onErrorResume$32(Mono.java:3887)
	at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:94)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onError(MonoFlatMap.java:172)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondError(MonoFlatMap.java:192)
	at reactor.core.publisher.MonoFlatMap$FlatMapInner.onError(MonoFlatMap.java:259)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:142)
	at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129)
	at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2400)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:171)
	at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2196)
	at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2070)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:96)
	at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:55)
	at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:157)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129)
	at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onNext(FluxHide.java:137)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129)
	at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onNext(FluxHide.java:137)
	at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
	at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1839)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:151)
	at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1839)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:151)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.complete(MonoIgnoreThen.java:292)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onNext(MonoIgnoreThen.java:187)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129)
	at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1839)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:151)
	at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
	at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1839)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:151)
	at reactor.core.publisher.Operators$MonoInnerProducerBase.complete(Operators.java:2666)
	at reactor.core.publisher.MonoSingle$SingleSubscriber.onComplete(MonoSingle.java:180)
	at reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onComplete(MonoFlatMapMany.java:260)
	at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onComplete(FluxContextWrite.java:126)
	at reactor.core.publisher.MonoUsing$MonoUsingSubscriber.onNext(MonoUsing.java:232)
	at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122)
	at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74)
	at reactor.core.publisher.FluxHandle$HandleSubscriber.onNext(FluxHandle.java:126)
	at reactor.core.publisher.FluxMap$MapConditionalSubscriber.onNext(FluxMap.java:224)
	at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:113)
	at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onNext(FluxHandleFuseable.java:191)
	at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
	at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1839)
	at reactor.core.publisher.MonoCollectList$MonoCollectListSubscriber.onComplete(MonoCollectList.java:129)
	at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260)
	at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144)
	at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:415)
	at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:439)
	at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:493)
	at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:789)
	at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:114)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
	at com.azure.core.http.netty.implementation.AzureSdkHandler.channelRead(AzureSdkHandler.java:224)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
	at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
	at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318)
	at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
	at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1475)
	at io.netty.handler.ssl.SslHandler.decodeNonJdkCompatible(SslHandler.java:1349)
	at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1389)
	at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530)
	at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918)

Azure Storage Accounts
Azure Storage Accounts
Globally unique resources that provide access to data management services and serve as the parent namespace for the services.
3,220 questions
Azure Blob Storage
Azure Blob Storage
An Azure service that stores unstructured data in the cloud as blobs.
2,919 questions
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. Marcin Policht 25,755 Reputation points MVP
    2024-11-07T14:39:45.92+00:00

    It looks like the code is correctly set up to authenticate but is encountering a "ResourceNotFound" error (404), which can happen due to several reasons in this scenario. You might want to try the following :

    1. Application Permissions and Role Assignment
      • Double-check that the Managed Identity or service principal you’re using has been granted the correct role at the Storage Account level or for specific containers within it.
      • Azure recommends using Storage Blob Data Contributor or Storage Blob Data Owner for the required permissions.
      • If you’re using a user-assigned managed identity, ensure that the identity has been assigned permissions on the Storage Account.
    2. Correct Scope in Role Assignment
      • Ensure that the permissions are granted at the correct scope. You can grant permissions at:
      • Storage Account level: which applies to all containers.
    • Container level: if permissions are set only at the container level, check if you’re accessing the correct container.
    • Try to confirm if the service principal or managed identity has the necessary permissions for the exact container you’re trying to access, not just the root account.
    1. Endpoint URL Check
      • Verify that the endpoint URL in the code is correctly set for accessing the blob storage. Sometimes, if there are additional characters or typos in the URL, it may cause a 404 error.
      • Ensure that the URL format (https://{accountName}.blob.core.windows.net) is correct, especially if you are accessing a specific container or blob within the storage account.
    2. Token Authentication in the Code
      • In your code snippet, ensure that credentials are initialized and passed correctly.
      • Confirm that the client is fetching and using the token correctly. The TokenCredential should provide the access token needed for Entra ID authentication.
    3. Testing with Minimal Permissions
      • To narrow down the issue, assign the Storage Blob Data Owner role to your service principal temporarily at the Storage Account level. This ensures the service principal has the highest level of data permissions to validate if it’s an issue with permissions.

    Example Code Modification (for the sake of clarity) Ensure that the secret and credential are defined properly and replace any placeholder values with actual credentials:

    public static void main(String[] args) {
    
        String accountName = "yourAccountName";
        String tenantId = "yourTenantId";
        String clientId = "yourClientId";
        String clientSecret = "yourClientSecret";
    
        TokenCredential credential = new ClientSecretCredentialBuilder()
                .tenantId(tenantId)
                .clientId(clientId)
                .clientSecret(clientSecret)
                .build();
    
        String endpoint = String.format(Locale.ROOT, "https://%s.blob.core.windows.net", accountName);
    
        BlobServiceClient storageClient = new BlobServiceClientBuilder()
                .endpoint(endpoint)
                .credential(credential)
                .buildClient();
    
        try {
            storageClient.getAccountInfo();
            System.out.println("Successfully connected to the storage account.");
        } catch (Exception e) {
            System.err.println("Error connecting to storage account: " + e.getMessage());
        }
    }
    

    Testing and Debugging Steps

    1. Run the code after adjusting configurations and permissions as above to see if the issue persists.
    2. Test connecting directly via a simpler Azure CLI or PowerShell command to rule out other issues.
    3. If you still encounter issues, use the Azure Storage Explorer with Entra ID authentication to connect to the storage account. This can help identify whether the problem lies with the configuration or the code.

    If the above response helps answer your question, remember to "Accept Answer" so that others in the community facing similar issues can easily find the solution. Your contribution is highly appreciated.

    hth

    Marcin

    0 comments No comments

  2. Vinod Kumar Reddy Chilupuri 660 Reputation points Microsoft Vendor
    2024-11-07T16:15:13.8833333+00:00

    Hi Saurabh Bhandari,

    Welcome to Microsoft Q&A, thanks for posting your query.
    The error you are facing "ResourceNotFound" error suggests that the Azure Storage account is not being accessed correctly using the MS Entra ID authentication method, even though it works with shared keys and SAS tokens.

    Please follow the below steps which may solves your issue.

    Verify Permissions and Roles:

    Verify the Azure AD application has the appropriate role with the required permissions on the storage account. The commonly used role for accessing is Storage Blob Data Contributor or Storage Blob Data Reader.

    Verify the Configuration and Setup:

    Confirm that the tenant ID, client ID, and client secret are accurately configured and passed to "ClientSecretCredentialBuilder".

    Enable Azure AD Authentication on the Storage Account:

    Confirm that Azure AD integration is enabled for the storage account.

    Navigate to Azure portal >> storage Account >> Access Control (IAM), check that the necessary roles are assigned.

    Verify that any Conditional Access policies in place allow the Azure AD application to access the storage account, especially if there are restrictions on location or multi-factor authentication (MFA) requirements.

    Network Configuration:

    Confirm that there are no network restrictions blocking access to the storage account. This includes checking the firewall settings, VNET configurations, and private endpoint settings.

    If a firewall is enabled on the storage account, check that the IP range or virtual network (VNET) where the application resides is permitted.

    If using a private endpoint, confirm that the application’s environment can access this endpoint, and that DNS resolution is correctly configured for the endpoint within the VNET.

    Blob Service Endpoints:

    Verify that the endpoint URL is correctly formatted and accessible. It should follow the format.

    "https://<account-name>.blob.core.windows.net".

    Ensure that this URL is accessible, to verify network connectivity from the client application’s environment to the storage account endpoint.

    Validate the Token Credential:

    Validate that the TokenCredientail is correctly generated and contains the necessary claims to access the storage account.

    • Audience (aud): Should match the resource URL, which is generally set to https://storage.azure.com/ for Azure Storage access.
    • Scope: Check that the token has the correct scope (e.g., https://storage.azure.com/.default) for the required access level.

    Code with Additional Debugging:

    import com.azure.identity.ClientSecretCredential;
    import com.azure.identity.ClientSecretCredentialBuilder;
    import com.azure.storage.blob.BlobServiceClient;
    import com.azure.storage.blob.BlobServiceClientBuilder;
    import com.azure.storage.blob.models.BlobStorageException;
    import com.azure.core.credential.TokenCredential;
    import java.util.Locale;
    public class Main {
        public static void main(String[] args) {
            String accountName = "{AccountName}";
            String tenantId = "{TenantID}";
            String clientId = "{ClientID}";
            String clientSecret = "{ClientSecret}";
            // Build the TokenCredential
            TokenCredential credential = new ClientSecretCredentialBuilder()
                    .tenantId(tenantId)
                    .clientId(clientId)
                    .clientSecret(clientSecret)
                    .build();
            String endpoint = String.format(Locale.ROOT, "https://%s.blob.core.windows.net", accountName);
            // Build the BlobServiceClient
            BlobServiceClient storageClient = new BlobServiceClientBuilder()
                    .endpoint(endpoint)
                    .credential(credential)
                    .buildClient();
            try {
                // Attempt to get account information
                storageClient.getAccountInfo();
                System.out.println("Successfully accessed the storage account using Azure AD authentication.");
            } catch (BlobStorageException e) {
                System.err.println("Failed to access the storage account. Error details:");
                System.err.println("Status code: " + e.getStatusCode());
                System.err.println("Error message: " + e.getServiceMessage());
                e.printStackTrace();
            } catch (Exception e) {
                System.err.println("An unexpected error occurred.");
                e.printStackTrace();
            }
        }
    }
    
    

    This code includes detailed error handling to capture status codes and error messages, which can help pinpoint the exact failure point in your configuration.

    Additional Debugging Steps:

    • Check Role Assignments in the Azure Portal.
    • Navigate to Access Control (IAM) > Role Assignment in the Azure Portal.
    • Confirm that the Azure AD application (using its client ID) has the Storage Blob Data Contributor or Storage Blob Data Reader role for the storage account.

    Verify Token Claims

    • Decode the generated token and verify its claims, particularly
    • The aud claim should match https://storage.azure.com.
    • Ensure that necessary permissions are included for blob access.

    Verify Network Access

    • If a private endpoint is configured, confirm that the application can connect to it and that DNS resolution points correctly within the VNET.

    Enable Logging

    • Enable diagnostic logging for both authentication attempts and request failures to get more detailed information. Azure Monitor or Application Insights can capture logs for further analysis. This will help in tracking access attempts and identifying any token validation issues.

    https://learn.microsoft.com/en-us/azure/storage/blobs/authorize-access-azure-active-directory

    Please feel free to contact if the issue persists, we will be glad to assist you closely. Please do consider clicking on "Accept Answer" and "Up-vote" on the post that helps you, as it can be beneficial to other community members.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.