Blazor server with web api controller authenticate issue - authentication

I have a Blazor server app that I want to add a web api controller to that can be accessed from Postman and eventually other apps. The Blazor app needs authentication, but not the web api. I tried adding AllowAnonymous, but I am getting an authentication error calling it from Postman:
HTTP Error 401.2 - Unauthorized
You are not authorized to view this page due to invalid authentication headers.
I suspect our security proxy is adding the headers:
Is it possible to host an unsecured (AllowAnonymous) web api inside an authenticated Blazor Server app?
Maybe I just need to craft my api call a certain way?
Controller:
[Route("api/[controller]")]
[ApiController]
[AllowAnonymous]
public class ProfileController : ControllerBase
{
[HttpGet("{year}", Name = "GetProfileResults")]
public async Task<IActionResult> GetProfileResults(int year)
{
var profileResults = repo.GetResults(year);
return Ok(profileResults);
}
}

You have to add another http client with no tokens attached.
Program.cs
builder.Services.AddHttpClient(
name: "Anon.ServerAPI",
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
RazorPage.razor.cs
[Inject]
public IHttpClientFactory HttpClientFactory { get; set; }
protected override async Task OnInitializedAsync()
{
http = HttpClientFactory.CreateClient("Anon.ServerAPI");
videos = await http.GetFromJsonAsync<VideoDto[]>("api/YoutubeVideos");
}

The key point to host a public API in a Blasor Server app is to ensure the API routing takes precedence over others.
In Program.cs (or Startup.cs):
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers(); // the order is important, this ensures API takes precedence.
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
Alternatively for endpoint routing:
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
app.MapControllers(); // the order is important, this ensures API takes precedence.
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
});
app.Run();
Next, the controller. In your example the code is completely correct. It must use [AllowAnonymous] at the controller level or at specific actions as usual.
[Route("api/[controller]")]
[ApiController]
[AllowAnonymous]
public class ProfileController : ControllerBase
{
[HttpGet("{year}", Name = "GetProfileResults")]
public async Task<IActionResult> GetProfileResults(int year)
{
var profileResults = repo.GetResults(year);
return Ok(profileResults);
}
}
That should be enough to route the call to API before Blazor takes over the security.
Last but not the least is the default exception configuration handling code added to Blazor projects by default:
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
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();
}
Please be aware that when this code is used any unhandled exceptions during an API call will be caught by the error handler which doesn't respect API [AllowAnonymous] settings and may trigger the authentication challenge configured for Blazor.

Related

Http Post in Orchard Core asp net core Web App returns bad request

I'm using Orchard core in asp net core web app project. I have a controller with two simple get and post Apis. As I'm using OrchardCore the Startup.cs file has different config and I dont use services.AddControllers() in configureServices.
Every thing is fine untill I'm using HttpGet. But when I want to have an Api with HttpPost postMan says badRequest. So I Added services.AddControllers() in Startup.cs and the post Api was fine in post Man but the orchard project says I have multipe Endpoints.
I used services.AddMvc().AddNewtonsoftJson(), and every thing was fine but the admin page didn't load and had error as below:
InvalidOperationException: The view 'Index' was not found. The
following locations were searched:
/Areas/OrchardCore.AdminDashboard/Views/Dashboard/Index.cshtml
/Areas/OrchardCore.AdminDashboard/Views/Shared/Index.cshtml
/Views/Shared/Index.cshtml /Pages/Shared/Index.cshtml
I wold appreciate it if you can help me how to call Post Api.
here is my code:
[HttpPost("post")]
public Task<string> post()
{
return Task.FromResult("hiPost");
}
[HttpGet("get")]
public Task<string> get()
{
return Task.FromResult("hiGet");
}
and this is my startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
//services.AddControllers();
services.AddOrchardCms();
services.AddMediatR(typeof(SelectedWebSiteBlogQueryHandler).Assembly);
services.AddAutoMapper(typeof(Startup));
services.AddCors();
services.AddMvc().AddNewtonsoftJson();
}
public void Configure(IApplicationBuilder app, IHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(o => o.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseOrchardCore();
}
}
You are probably missing an IgnoreAntiForgeryToken attribute on your controller.
AntiForgery is enabled by default by OrchardCore
For an ApiController in OrchardCore I would expect to see the controller decorated as follows.
[ApiController]
[Authorize(AuthenticationSchemes = "Api"), IgnoreAntiforgeryToken, AllowAnonymous]
However this depends if you are using the OpenId module to authenticate with, or simply need to post to a normal controller, without an AuthenticationScheme
Depending on what you are actually posting from in real life, it may be better to supply an anti forgery token as part of your post.

Blazor Server get current logged user when EF DbContext execute select data query

I build blazor server multi tenant application and I wants to use HasQueryFilter in entity framework DbContext for predefined filter with TenantId. Ofcourse I have connected User with tenant. I created CurrentTenatnProvider which has method GetCurrentTenatnId. In this mehtod i use AuthentificationStateProvider and call GetAuthenticationStateAsync(). Ofcourse i get the error ''GetAuthenticationStateAsync was called before SetAuthenticationState". I Cannot use IHttpContextAccessor because in Azure app I get null reference exception.
Is there any other possibility how to get CurrentUser in time when DbContext execute select data query?
I was thinking about cache CurrnetUser but there is problem with cache key.CurrentTenatnProvider service is registered as scoped service. There is Id attribute which is set in constructor. And then is used as cache key. But this approach does not working and I get the same error.
It si possible get signal-r connection identificator and use it as cache key?
I spended 2 days with test lots of combination and read lots of documentation but unfortunately I didn't find any solution. I will be very grateful for any advice.
I have done this a few ways but the simplest way I could find was retrieving my user from the database in the MainLayout.razor file during OnInitializedAsync() and passing it in a fixed cascading parameter <CascadingValue Name="CurrentUser" IsFixed="true" Value="UserId">. From there, I could reference it where needed in any child component.
finally I found solution! From my view it is bug! Problem is because services.AddDbContextFactory is registered as Singleton. I create my own implementation of IDbContext factory and register it as Scoped. After this change everything’s works perfect. When I change registration scope of DbContextFactory to singleton, I get the error: GetAuthenticationStateAsync was called before SetAuthenticationState.
My DbContextFactory
public class BlazorContextFactory<TContext> : IDbContextFactory<TContext> where TContext : DbContext
{
private readonly IServiceProvider provider;
public BlazorContextFactory(IServiceProvider provider)
{
this.provider = provider;
}
public TContext CreateDbContext()
{
if (provider == null)
{
throw new InvalidOperationException(
$"You must configure an instance of IServiceProvider");
}
return ActivatorUtilities.CreateInstance<TContext>(provider);
}
}
My StartUp
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ApplicationDbContext>();
services.AddScoped<IDbContextFactory<ApplicationDbContext>, BlazorContextFactory<ApplicationDbContext>>();
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddSingleton<WeatherForecastService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
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.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
I hope it's help other peoples! I spend 6 days with this problem :(

Azure AD - Expose an API to consume it with application permissions

I'm developing a custom API and a windows service. I want to use Azure AD for authentication and authorization between the api and the windows service.
My question ist: How can I expose the API, so that I can add the api permissions to the win service with the type "application permissions"?
I can only select "delegated permissions" when I want to add the api permissions to the win service. But I need "application persmissions" because the win service runs without an user.
Thank you in forward!
Best regards
Matthias
Ok. Now I know how to set up the manifest in the app registration. I also get a bearor token and in the bearor token I can see (if I bas64-decode it) the Client ID of the Web API and also the App roles: "roles":["User.Sync2"]
So I think that the token is correct.
In the second step I call the Web API (https://localhost:44358/api/AzureADB2C/Ping) with authentication "Bearor" and the token. But then I receive a 401. (I have not registered any platform in the app registration for the Web API and therefore also no redirect URI. But I think I don't need it?)
Here's the Startup.cs of my Web API project (It's standard generated with Visual Studio):
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.AzureAD.UI;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace AzureADB2CConnect.API
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
and here is my API Controller:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace AzureADB2CConnect.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AzureADB2CController : ControllerBase
{
[HttpGet("Ping")]
[Authorize(Roles = "User.Sync2")]
//[Authorize]
public async void GetPing()
{
//foreach(Claim claim in ClaimsPrincipal.Current.Claims)
//{
//}
}
}
}
If I remove the "Authorized"-Tags I can call the API. And it doesn't matter If I use only Authorize or Authorize(Roles = "User.Sync2") I always receive a 401.
Where is the error/bug?
Thank you in forward!
Here is the decoded bearor token:
That's how I call the GET-Method to get the token:
you need to add app roles. Please follow How to: Add app roles in your application and receive them in the token. App role allowedMemberTypes should include Application.
I can reproduce your problem. You entered the wrong parameters when verifying the token. You need to change Authorized to Authorization and Bearor to Bearer.

CORS problem with custom controller and CustomClientStore in IdentityServer4

I want to add a custom end-point into IdentityServer4 but when I call API from another site, I have a CORS error.
I use a CustomClientStore to load my clients so i need to add CustomCorsPolicyService
Access to XMLHttpRequest at 'http://localhost:8082/embedded/log' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
In Startup, I add my CustomClientStore and CustomCorsPolicyService
public void ConfigureServices(IServiceCollection services)
{
...
CustomClientStore.Init(_Configuration);
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiScopes(Config.GetApiScopes())
.AddRedirectUriValidator<MyUriValidator>();
builder.Services.AddSingleton<IUserRepository, UserRepository>();
builder.AddProfileService<CustomProfileService>();
builder.AddClientStore<CustomClientStore>();
//services.AddCors(setup => setup.AddDefaultPolicy(b => b.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()));
builder.AddCorsPolicyService<CustomCorsPolicyService>();
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
// Add this before any other middleware that might write cookies
app.UseCookiePolicy();
app.UseIdentityServer();
app.UseRouting();
app.UseCors();
app.UseMvcWithDefaultRoute();
// This will write cookies, so make sure it's after the cookie policy
app.UseAuthentication();
}
In My Controller
[ApiController]
public sealed class EmbeddedLogController : ControllerBase
{
[HttpPost]
[Route("/embedded/log/")]
[EnableCors()]
public ActionResult Log(ParametersLog parameters)
{
....
}
}
Without CustomClientStore I could call services.AddCors(setup => setup.AddDefaultPolicy... to accept CORS
But now I need to use builder.AddClientStore<CustomClientStore>(); because of CustomProfileService.
How can I fix that ?
Thanks
this GitHub issue might give you some clues.
That says:
Solved When using Endpoint Routing CORS and IdentityServer4, the call
to UseCors() must be after UseRouting() but BEFORE UseIdentityServer()
and UseAuthorization(). Otherwise it will appear to work but
Pre-Flight checks will fail

ASP .Net Core Google Authentication

I have a problem with google authentication on my .net core web api application.
My use case is simple, get bearer token from google put token in authorization header as "Bearer {token}" and call my web api.
But I cannot make it work. After I get token from google on following url:
https://accounts.google.com/o/oauth2/v2/auth?scope=email%20openid&include_granted_scopes=true&state=some_test_state&redirect_uri=http%3A%2F%2Flocalhost%3A53512&response_type=token&client_id={someClientID}
I will make call to my api with header:
Authorization: Bearer {TokenValue}
But every time I'm getting 401 Unauthorized.
This is my Startup class:
public static IConfigurationRoot Configuration { get; private set; }
// This method gets called by the runtime. Use this method to add services to the container
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Pull in any SDK configuration from Configuration object
services.AddDefaultAWSOptions(Configuration.GetAWSOptions());
// Add S3 to the ASP.NET Core dependency injection framework.
services.AddAWSService<Amazon.S3.IAmazonS3>();
IocConfig.Configure(services);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddLambdaLogger(Configuration.GetLambdaLoggerOptions());
var googleOptions = new GoogleOptions
{
AuthenticationScheme = "Google",
ClientId = "clientid",
ClientSecret = "cs",
SignInScheme = "Google"
};
app.UseGoogleAuthentication(googleOptions);
app.UseDeveloperExceptionPage();
app.UseMvc();
}
It's because your authentication scheme is "Google", but if you want to use bearer token you need to add it to your startup.cs
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// here's your options
})
And use this authentication scheme instead of "Google"