Problem to generate blob storage SAS-token in WebApp

Anton Tokola 0 Reputation points
2024-03-18T16:14:03.3+00:00

Hi,

First I want to let you know that I'm beginner with Azure.

I have a problem to generate SAS-token (view-access token) for my blob storage container in my webapp server code. I'll get an 403 (unauthorized) error when trying to generate the token. I have a local server project, and nearly "cloned" project which is in my webapp server. The code works in my local server project, and generates SAS-token successfully, and I think the reason for that is I'm using my Azure - "owner" account, which have all rights to do what ever want to.

But when I try to generate SAS-token with my WebApp, it's resource role doesn't have rights to do that (regarding the 403 unauthorized error). I have assigned the "Storage Blob Data Contributor", "Storage Blob Data Owner", and "Storage Blob Delegator" roles in blob storage for my WebApp. I have also allowed my webapp's ip-addresses in the blob storage network-settings (images attached). I first used the blob storage access key in my code (with keyvault), and it worked on my local project. But I changed it later to use "DefaultAzureCredential()" in my code = to use recourse's own role (?).

When trying with "Enabled from selected virtual networks and IP addresses"-set up on my blob storage network settings, I'll get this error:

"Error uploading image: This request is not authorized to perform this operation.\nRequestId:b4047121-001e-0048-654e-79dc55000000\nTime:2024-03-18T16:11:01.2061918Z\r\nStatus: 403 (This request is not authorized to perform this operation.)\r\nErrorCode: AuthorizationFailure\r\n\r\nContent:\r\n<?xml version=\"1.0\" encoding=\"utf-8\"?><Error><Code>AuthorizationFailure</Code><Message>This request is not authorized to perform this operation.\nRequestId:b4047121-001e-0048-654e-79dc55000000\nTime:2024-03-18T16:11:01.2061918Z</Message></Error>\r\n\r\nHeaders:\r\nServer: Microsoft-HTTPAPI/2.0\r\nx-ms-request-id: b4047121-001e-0048-654e-79dc55000000\r\nx-ms-client-request-id: e00d19bd-a0f6-4ae7-8e1b-3b486154609b\r\nx-ms-error-code: AuthorizationFailure\r\nDate: Mon, 18 Mar 2024 16:11:00 GMT\r\nContent-Length: 246\r\nContent-Type: application/xml\r\n",

When I tried to allow all the traffic with "Enabled from all networks"-set up on my blob storage, I got this error:

"Error uploading image: Value cannot be null. (Parameter 'sharedKeyCredential')",

I have the same problem with Key Vault (works with local but not in webapp), but I would like to figure blob storage problem first. I have assigned these roles for webapp in my keyvault resource: "Key Vault Certificates Officer", "Key Vault Secrets User", and "Reader".

I'll give parts of my WebApp's Program.cs-class, and other classes where you can check if there is something wrong in my code.

It may be some stupid mistake, but I'm pretty new with these..

Thank you!

//PROGRAM.CS

using IdentityModel.Client;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Perikato.Data;
using Perikato.Services;
using System.Net;
using System.Security.Claims;
using Microsoft.EntityFrameworkCore.Design;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Text;
using Swashbuckle.AspNetCore.SwaggerGen;
using Microsoft.OpenApi.Models;
using MongoDB.Driver;
using System.Security.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Perikato.Services.MongoDbServices;
using FirebaseAdmin;
using Google.Apis.Auth.OAuth2;
using Azure.Storage.Blobs;
using Azure.Identity;


var builder = WebApplication.CreateBuilder(args);
OIDC_Service OIDC_service = new OIDC_Service();

builder.Services.AddControllers();
builder.Services.AddLogging();
builder.Logging.AddConsole();
builder.Logging.SetMinimumLevel(LogLevel.Debug);

builder.Services.AddControllers().AddNewtonsoftJson(options =>
{
    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});

builder.Services.AddEndpointsApiExplorer();

ConfigurationManager configuration = builder.Configuration;

//Azure keys from KeyVault
var (identityTokenCertificate,
    OIDC_client_id,
    OIDC_client_secret,
    PerikatoDBpass,
    PerikatoMongoDBpass,
    FirebaseServerKey,
    AzureBlobStorageConnectionString) = OIDC_Service.ConnectToAzureKeyVault(builder.Configuration);
string firebaseServerKeyString = FirebaseServerKey.Value;

FirebaseApp.Create(new AppOptions()
{
    Credential = GoogleCredential.FromJson(firebaseServerKeyString),
});

string containerEndpoint = $"https://perikatoblobfiles.blob.core.windows.net/perikatopackageimages";
//Azure Blob Storage to DI-container
builder.Services.AddSingleton(x => new BlobServiceClient(new Uri(containerEndpoint), new DefaultAzureCredential()));

// MS-SQL
string connectionString = $"Server=tcp:perikatoserver.database.windows.net,1433;Initial Catalog=PerikatoDB;Persist Security Info=False;User ID=perikatodb;Password={PerikatoDBpass.Value};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;";
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));

// MongoDB
string mongoDbConnectionString = $"mongodb://mongogeodb:{PerikatoMongoDBpass.Value}@mongogeodb.mongo.cosmos.azure.com:10255/?ssl=true&retrywrites=false&replicaSet=globaldb&maxIdleTimeMS=120000&appName=@mongogeodb@";
var mongoClientSettings = MongoClientSettings.FromConnectionString(mongoDbConnectionString);
mongoClientSettings.SslSettings = new SslSettings() { EnabledSslProtocols = SslProtocols.Tls12 };

// MongoDB
builder.Services.AddSingleton<IMongoClient>(serviceProvider => new MongoClient(mongoClientSettings));
string databaseName = configuration["MongoDbDatabaseName"];
builder.Services.AddSingleton(serviceProvider =>
    new GoogleMapsLegsService(serviceProvider.GetRequiredService<IMongoClient>(), databaseName, "GoogleMapsLegs"));
builder.Services.AddSingleton(serviceProvider =>
    new DealLocationsService(serviceProvider.GetRequiredService<IMongoClient>(), databaseName, "DealLocations"));

builder.Services.AddSingleton<MapMatchesService>();
builder.Services.AddScoped<BlobStorageService>();
builder.Services.AddScoped<FirebaseService>();
builder.Services.AddScoped<UserDataHandler>();
builder.Services.AddSingleton<IUserClaimsService, UserClaimsService>();

//CORS
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(builder =>
    {
        builder.WithOrigins("http://localhost:8080")
               .AllowAnyHeader()
               .AllowAnyMethod()
               .AllowCredentials();
    });
});

builder.Services.AddHttpClient();

builder.Services.Configure<CookiePolicyOptions>(options =>
{
    options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
    options.OnAppendCookie = cookieContext =>
        CheckSameSite(cookieContext.CookieOptions);
    options.OnDeleteCookie = cookieContext =>
        CheckSameSite(cookieContext.CookieOptions);
});
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    // Muut mahdolliset asetukset...
});
builder.Services.AddControllersWithViews();
var app = builder.Build();
app.UseExceptionHandler("/FTN/AuthenticationFailed");
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI();

app.UseCors();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.UseCookiePolicy();
app.UseForwardedHeaders();
app.Run();



// BLOBSTORAGESERVICE.CS

using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Sas;
using Microsoft.AspNetCore.Http;
using System;
using System.IO;
using System.Threading.Tasks;
namespace Perikato.Services
{
    public class BlobStorageService
    {
        private readonly BlobServiceClient _blobServiceClient;
        public BlobStorageService(BlobServiceClient blobServiceClient)
        {
            _blobServiceClient = blobServiceClient;
        }
        public async Task<(string, string)> UploadImageAsync(IFormFile image, string containerName)
        {
            var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
            var blobClient = containerClient.GetBlobClient(image.FileName);
            var uploadResponse = string.Empty;
            var sasUrl = string.Empty;
            try
            {
                using (var stream = image.OpenReadStream())
                {
                    await blobClient.UploadAsync(stream, new BlobHttpHeaders { ContentType = image.ContentType });
                    uploadResponse = blobClient.Uri.AbsoluteUri;
                }
                var sasBuilder = new BlobSasBuilder
                {
                    BlobContainerName = containerName,
                    BlobName = image.FileName,
                    Resource = "b", // 'b' for blob
                    StartsOn = DateTimeOffset.UtcNow.AddMinutes(-5),
                    ExpiresOn = DateTimeOffset.UtcNow.AddDays(7),
                };
                sasBuilder.SetPermissions(BlobSasPermissions.Read);
                sasUrl = blobClient.GenerateSasUri(sasBuilder).AbsoluteUri;
            }
            catch (Exception ex)
            {
                // Log error
                uploadResponse = $"Error uploading image: {ex.Message}";
            }
            return (uploadResponse, sasUrl); //Error response will be saved to SQL
        }
    }
}

User's image

User's image

Azure Key Vault
Azure Key Vault
An Azure service that is used to manage and protect cryptographic keys and other secrets used by cloud apps and services.
1,098 questions
Azure Blob Storage
Azure Blob Storage
An Azure service that stores unstructured data in the cloud as blobs.
2,409 questions
Azure Role-based access control
Azure Role-based access control
An Azure service that provides fine-grained access management for Azure resources, enabling you to grant users only the rights they need to perform their jobs.
655 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Amrinder Singh 1,730 Reputation points Microsoft Employee
    2024-03-18T16:32:24.8166667+00:00

    Hi @Anton Tokola - Thanks for reaching out over Q&A Forum.

    From the AD roles permissions standpoint, roles provided are apt to perform Data Plane Operation on the storage account. Also, from the test on the local, this is more inline towards auth failing on the Networking level.

    Is your storage account hostes in same region of that of Web App? If yes, the even though you whitelist the public IP of the Web App, the connection shall happen via underlying backbone and it will hit the storage with a private IP. In that case, the call would tend to fail with IPAuthorization error at the backend. You can verify this by enabling the diagnostic logging as well:

    https://learn.microsoft.com/en-us/azure/storage/blobs/monitor-blob-storage?tabs=azure-portal

    I would recommend putting your Web App in a VNET and then whitelist the VNET itself on the storage account level.

    Please test this out and let me know if that helps out ahead.

    0 comments No comments