OiO.lk Community platform!

Oio.lk is an excellent forum for developers, providing a wide range of resources, discussions, and support for those in the developer community. Join oio.lk today to connect with like-minded professionals, share insights, and stay updated on the latest trends and technologies in the development field.
  You need to log in or register to access the solved answers to this problem.
  • You have reached the maximum number of guest views allowed
  • Please register below to remove this limitation

Blazor Server. How do I connect to my API protected with [Authorize]? Works fine on my laptop but not on Azure

  • Thread starter Thread starter Mark Evans
  • Start date Start date
M

Mark Evans

Guest
I have a Blazor server application. I created this using the template in Visual Studio for a Blazor Web App with Authentication Type = Individual Accounts. I have also added an API for users to download files. This is a business app and everything needs to be secured.

Here's what my API looks like:

Code:
[Authorize(Policy = "Admins")]
[Microsoft.AspNetCore.Mvc.Route("api/[controller]")]
[ApiController]
public class DataDownloadController : ControllerBase
{.....

And here's the code in my Blazor Server app that calls it:

Code:
var request = new HttpRequestMessage(HttpMethod.Get, fullUrl);  
var cookies = HttpContextAccessor.HttpContext.Request.Cookies;

foreach (var cookie in cookies)
    request.Headers.Add("Cookie", $"{cookie.Key}={cookie.Value}");

var response = await Http.SendAsync(request);

if (response.IsSuccessStatusCode)
{.....

It all works fine on my laptop but when deployed to an AppService on Azure things go wrong. HttpContextAccessor.HttpContext is always null so I can't get the cookies. I get the feeling that I'm doing something fundamentally wrong here. It seems silly that I have to manually deal with these cookies like this.

This data is not critically sensitive, and the API doesn't have any functionality to update data, so all I want is some kind of minimal security on this API without spending a huge amount of time on this. It would be really nice if I didn't have to change the existing account and security arrangements for the Blazor Server app because it's working nicely.

Thanks for helping. I've been stuck on this for hours and it's doing my frigin' head in!!

Here's my program.cs:

Code:
public static void Main(string[] args)
{
    var builder = WebApplication.CreateBuilder(args);

    builder.Services.AddHttpContextAccessor();
    builder.Services.AddScoped<ErrorHandler>();         

    builder.Services.AddRazorComponents()
        .AddInteractiveServerComponents();

    builder.Services.AddCascadingAuthenticationState();  // seems to work OK without this but it's part of the default template
    builder.Services.AddScoped<IdentityUserAccessor>();
    builder.Services.AddScoped<IdentityRedirectManager>();
    builder.Services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();

    // Added to get access to user obj
    builder.Services.AddServerSideBlazor(); // no idea if this is needed, gemini suggested this
    //builder.Services.AddScoped<AuthenticationStateProvider, DefaultAuthenticationStateProvider>();
    builder.Services.AddAuthorizationCore();

    builder.Services.AddAuthentication(options =>
    {
        options.DefaultScheme = IdentityConstants.ApplicationScheme;
        options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
    })
    .AddIdentityCookies();

    builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
                        .AddRoles<IdentityRole>()
                        .AddEntityFrameworkStores<ApplicationDbContext>()
                        .AddSignInManager()
                        .AddDefaultTokenProviders();

    builder.Services.AddScoped<IUserService, UserService>();
    builder.Services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, CustomUserClaimsPrincipalFactory>();

    builder.Services.AddAuthorization(options =>
    {
        options.AddPolicy("LatzAdminPolicy", policy =>
            policy.RequireAssertion(context =>
                context.User.HasClaim(c => c.Type == "IsLatzAdmin" && c.Value == "True")));
    });

    // This is my user utility service from the logic layer
    builder.Services.AddScoped<IUserService, UserService>();

    var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");

    builder.Services.AddDbContextFactory<ApplicationDbContext>(options =>
    {
        options.UseSqlServer(connectionString, sqlOptionsBuilder =>
            sqlOptionsBuilder.EnableRetryOnFailure(maxRetryCount: 10, maxRetryDelay: TimeSpan.FromSeconds(15), errorNumbersToAdd: null));
    });

    builder.Services.AddQuickGridEntityFrameworkAdapter();
    builder.Services.AddApplicationInsightsTelemetry(options =>
    {
        options.ConnectionString = builder.Configuration["ApplicationInsights:InstrumentationKey"];
    });

    builder.Services.Configure<LogoSettings>(builder.Configuration.GetSection("LogoSettings"));
    builder.Services.AddSingleton(resolver => resolver.GetRequiredService<IOptions<LogoSettings>>().Value);

    builder.Services.AddScoped<IDataService, DataService>();
    builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration.GetSection("AuthMessageSenderOptions"));
    builder.Services.AddScoped<IEmailSender<ApplicationUser>, EmailSender>();
    builder.Services.AddScoped<IEmailSender, EmailSender>();
    builder.Services.AddScoped<EmailSender>();
    builder.Services.AddScoped<ICustomEmailSender, CustomEmailSender>();
    builder.Services.AddScoped<CustomEmailSender>();
    builder.Services.AddSingleton<BlobStorageService>();
    builder.Services.AddHttpClient();
    builder.Services.AddControllers();

    var app = builder.Build();

    // Configure the HTTP request pipeline.
    if (app.Environment.IsDevelopment())
    {
        app.UseMigrationsEndPoint();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        //The default HSTS value is 30 days.You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();


    app.UseRouting();
    app.UseAntiforgery();
    // app.UseAuthentication();// ai reckons this isn't needed for blazor server, only normal asp.net 
    app.UseAuthorization();

    app.MapRazorComponents<App>()
        .AddInteractiveServerRenderMode();

    // Add additional endpoints required by the Identity /Account Razor components.
    app.MapAdditionalIdentityEndpoints();
    app.MapControllers();

    app.Run();
}
<p>I have a Blazor server application. I created this using the template in Visual Studio for a Blazor Web App with Authentication Type = Individual Accounts. I have also added an API for users to download files. This is a business app and everything needs to be secured.</p>
<p>Here's what my API looks like:</p>
<pre><code>[Authorize(Policy = "Admins")]
[Microsoft.AspNetCore.Mvc.Route("api/[controller]")]
[ApiController]
public class DataDownloadController : ControllerBase
{.....
</code></pre>
<p>And here's the code in my Blazor Server app that calls it:</p>
<pre><code>var request = new HttpRequestMessage(HttpMethod.Get, fullUrl);
var cookies = HttpContextAccessor.HttpContext.Request.Cookies;

foreach (var cookie in cookies)
request.Headers.Add("Cookie", $"{cookie.Key}={cookie.Value}");

var response = await Http.SendAsync(request);

if (response.IsSuccessStatusCode)
{.....
</code></pre>
<p>It all works fine on my laptop but when deployed to an AppService on Azure things go wrong. HttpContextAccessor.HttpContext is always null so I can't get the cookies. I get the feeling that I'm doing something fundamentally wrong here. It seems silly that I have to manually deal with these cookies like this.</p>
<p>This data is not critically sensitive, and the API doesn't have any functionality to update data, so all I want is some kind of minimal security on this API without spending a huge amount of time on this. It would be really nice if I didn't have to change the existing account and security arrangements for the Blazor Server app because it's working nicely.</p>
<p>Thanks for helping. I've been stuck on this for hours and it's doing my frigin' head in!!</p>
<p>Here's my program.cs:</p>
<pre><code>public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<ErrorHandler>();

builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();

builder.Services.AddCascadingAuthenticationState(); // seems to work OK without this but it's part of the default template
builder.Services.AddScoped<IdentityUserAccessor>();
builder.Services.AddScoped<IdentityRedirectManager>();
builder.Services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();

// Added to get access to user obj
builder.Services.AddServerSideBlazor(); // no idea if this is needed, gemini suggested this
//builder.Services.AddScoped<AuthenticationStateProvider, DefaultAuthenticationStateProvider>();
builder.Services.AddAuthorizationCore();

builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies();

builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();

builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, CustomUserClaimsPrincipalFactory>();

builder.Services.AddAuthorization(options =>
{
options.AddPolicy("LatzAdminPolicy", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(c => c.Type == "IsLatzAdmin" && c.Value == "True")));
});

// This is my user utility service from the logic layer
builder.Services.AddScoped<IUserService, UserService>();

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");

builder.Services.AddDbContextFactory<ApplicationDbContext>(options =>
{
options.UseSqlServer(connectionString, sqlOptionsBuilder =>
sqlOptionsBuilder.EnableRetryOnFailure(maxRetryCount: 10, maxRetryDelay: TimeSpan.FromSeconds(15), errorNumbersToAdd: null));
});

builder.Services.AddQuickGridEntityFrameworkAdapter();
builder.Services.AddApplicationInsightsTelemetry(options =>
{
options.ConnectionString = builder.Configuration["ApplicationInsights:InstrumentationKey"];
});

builder.Services.Configure<LogoSettings>(builder.Configuration.GetSection("LogoSettings"));
builder.Services.AddSingleton(resolver => resolver.GetRequiredService<IOptions<LogoSettings>>().Value);

builder.Services.AddScoped<IDataService, DataService>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration.GetSection("AuthMessageSenderOptions"));
builder.Services.AddScoped<IEmailSender<ApplicationUser>, EmailSender>();
builder.Services.AddScoped<IEmailSender, EmailSender>();
builder.Services.AddScoped<EmailSender>();
builder.Services.AddScoped<ICustomEmailSender, CustomEmailSender>();
builder.Services.AddScoped<CustomEmailSender>();
builder.Services.AddSingleton<BlobStorageService>();
builder.Services.AddHttpClient();
builder.Services.AddControllers();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
//The default HSTS value is 30 days.You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();


app.UseRouting();
app.UseAntiforgery();
// app.UseAuthentication();// ai reckons this isn't needed for blazor server, only normal asp.net
app.UseAuthorization();

app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();

// Add additional endpoints required by the Identity /Account Razor components.
app.MapAdditionalIdentityEndpoints();
app.MapControllers();

app.Run();
}
</code></pre>
Continue reading...
 
Top