Cors issue with Blazor webassembly standalone client - blazor-client-side

I am struggling with some cors issue for DELETE and POST.
GET works fine.
Setup is: .Net core API in Azure API Management and Blazor webassembly standalone client (Azure app service) that calls the API.
Error I get when try to DELETE is.
"has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present"
So I find it difficult to understand what code that is needed here. It is so many example everywhere on CORS for .net core and also the different Blazor setups (server, hosted wasm etc).
I guess I need to handle the preflight request in some way for this to work?
This is what I use right now:
My ServiceTimes.razor that calls the API
#code {
private const string ServiceEndpoint = "https://MyProdAPI.azure-api.net/api/ServiceTimes";
private LWS_ServiceTimes[] servicetimes;
LWS_ServiceTimes servicetimeObj = new LWS_ServiceTimes();
string ids = "0";
bool showAddrow = false;
bool loadFailed;
protected override async Task OnInitializedAsync()
{
ids = "0";
try
{
servicetimes = await Http.GetFromJsonAsync<LWS_ServiceTimes[]>(ServiceEndpoint);
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
}
// Delete Method
protected async Task DeleteServiceTimes(long ServiceTimesID)
{
showAddrow = false;
ids = ServiceTimesID.ToString();
await Http.DeleteAsync("https://MyprodAPI.azure-api.net/api/ServiceTimes/1"); //Deletes the ID=1
}
Blazor webassembly standalone client Program.cs
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://prodsite.azurewebsites.net") });
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://xxxxxxxx-xxxx-xxxx-xxxx-xxxx/API.Access"); //API.Acess my scope in API
});
builder.Services.AddHttpClient("ServerAPI",
client => client.BaseAddress = new Uri("https://MyprodAPI.azure-api.net"))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("ServerAPI"));
await builder.Build().RunAsync();
}
API Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<LwsSpiderContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MyDatabase")));
services.AddSwaggerGen();
services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("https://prodsite.azurewebsites.net");
builder.AllowAnyMethod();
builder.WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization, "x-custom-header");
builder.AllowCredentials();
});
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
// app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("MyAllowSpecificOrigins");
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

Related

asp.net Core Add Response Headers in Middleware after UseRouting

I'm trying to add a server-side latency header through middleware. I've looked at several SO posts that have similar questions, but their solutions aren't working for me. Here's what I have in my Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory factory)
{
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
});
app.MyHeaderMiddleware();
}
I have to register MyHeaderMiddleware after UseRouting and UseEndpoints because it also needs the endpoint data.
Then in a extension method class, I've got the following.
public static void MyHeaderMiddleware(this IApplicationBuilder app)
{
app.MyHeaderMiddleware((context, logger) =>
{
var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
int status = context.Response.StatusCode;
if (context.Request.Host != null)
{
logger.PutProperty("Host", context.Request.Host.Value);
}
if (context.Request?.HttpContext?.Connection?.RemoteIpAddress != null)
{
logger.PutProperty("SourceIp", context.Request.HttpContext.Connection.RemoteIpAddress.ToString());
}
if (context.Request.Headers.TryGetValue("X-Forwarded-For", out StringValues value) && !String.IsNullOrEmpty(value) && value.Count > 0)
{
logger.PutProperty("X-Forwarded-For", value.ToArray());
}
return Task.CompletedTask;
});
}
public static void MyHeaderMiddleware(this IApplicationBuilder app, Func<HttpContext, IMetricsLogger, Task> metricsSetup)
{
app.Use(async (context, next) =>
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
var logger = context.RequestServices.GetRequiredService<IMetricsLogger>();
await metricsSetup(context, logger);
context.Response.OnStarting(() =>
{
stopWatch.Stop();
context.Response.Headers.Add("X-Server-Side-Latency", stopWatch.ElapsedMilliseconds.ToString());
return Task.CompletedTask;
});
await next();
});
}
If I register my middleware before UseRouting, the header is added as expected, but if it comes after, it executes, but the header is not seen in the response. I need to setup my metrics logger as part of this because I'm both recording the server-side latency for every API call as well as putting it in the http response.
I changed some of the ordering of steps that ended up getting this to work. The major change was this.
await next(); // this will make the endpoints available in the logger setup
await metricsSetup(context, logger);
and to a lesser degree, this:
app.UseRouting();
app.UseEmfMiddleware();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
});
By calling next() before the metrics logger is setup, the endpoints are made available even though they come after this middleware in the startup.

Blazor Open ID Connect authentication error "The request included multiple client credentials"

I have implemented Open ID Connect with Blazor using the following method:
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
this.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.AddRazorPages();
services.AddServerSideBlazor();
services.AddSignalR(e =>
{
e.MaximumReceiveMessageSize = 102400000;
});
services.AddBlazoredModal();
services.AddHttpClient();
services.AddScoped<AccessTokenStorage>();
services.AddAuthentication(opt =>
{
opt.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opt.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
}).AddCookie().AddOpenIdConnect("oidc", options =>
{
options.Authority = Credentials.Authority;
options.ClientId = Credentials.ClientId;
options.ClientSecret = Credentials.ClientSecret;
options.ResponseType = "code";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.UseTokenLifetime = false;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name" };
options.Events = new OpenIdConnectEvents
{
OnAccessDenied = context =>
{
context.HandleResponse();
context.Response.Redirect("/");
return Task.CompletedTask;
},
};
});
}
// 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();
}
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.UseAuthentication();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
Another vital part:
Login.cshtml.cs
public class LoginModel : PageModel
{
public async Task OnGet(string redirectUri)
{
await HttpContext.ChallengeAsync("oidc", new AuthenticationProperties {
RedirectUri = redirectUri });
}
}
It seems to work OK with demo.identityserver.io.
However, when changing it to my company identity provider, sometimes I retrieve the following error:
FBTOAU228E The request included multiple client credentials. OAuth 2.0
protocol requests can have one client credential only. For example,
the request cannot have client credentials in both the BA header and
the request body.
Is this a Blazor side issue or problem with the identity provider?
It happens seemingly at random, but it always happens when removing aspnetcore cookie in browser. Doing this should just get you back to the login screen, but throws this error instead. (Does not happen with demo.identiserver.io...)
Solved it. Seems this is problematic line:
options.GetClaimsFromUserInfoEndpoint = true;
I removed it/set it to false and it works like it should. I had to get the claims a little bit different.

CORS policy issue when consuming ASP NET Core 3.1 Web Api by Angular 8

I have encountered issue with CORS policy when developing Angular 8, ASP NET Core Web Api web application. My angular app is running on http://localhost:4200
There is one service created for communication with Web Api. It looks as follows
#Injectable({
providedIn: 'root'
})
export class AuthenticationService {
apiUrl: string = "";
constructor(private http: HttpClient) {
this.apiUrl = 'https://localhost:44316';
}
login(Username: any, Password: any){
return this.http.post<Observable<ResultItem<AuthenticationResponse>>>(this.apiUrl + "/api/User/Authenticate", {Username: Username, Password: Password});
}
}
Services is later called within component, but it is simply injected, and used with subscribe method.
onLogin(){
this.authenticationService.login(this.loginFormValues.username.value, this.loginFormValues.password.value).subscribe(
result => {});
}
Web Api is running seperatly, on https://localhost:44316/
End point for the method called from Angular looks as follows:
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
private readonly IUserService userService;
public UserController(IUserService userService)
{
this.userService = userService;
}
[HttpPost("Authenticate")]
public async Task<IActionResult> Authenticate(AuthenticationModel model)
{
return Ok(await userService.Login(model));
}
}
What I am most concerned about is my Startup file. So far, I have tried to change the CORS setting there, but with no successful results. Code of the Startup.cs file looks as follows.
Quick note:
Two lines of code within ConfigureServices method use some of my external functions, and their purpose is to:
AddSubstracture: registers all repositories as transients and registers DbContext.
AddApplication: registers services which are one layer above repositories as transients
Startup.cs code looks as follows
public class Startup
{
private IServiceCollection _services;
public Startup(IConfiguration configuration, IWebHostEnvironment environment)
{
Configuration = configuration;
Environment = environment;
SportFacilityUnitSettings = configuration.Get<SportFacilityUnitSettings>();
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment Environment { get; }
public SportFacilityUnitSettings SportFacilityUnitSettings { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddMvc(option => option.EnableEndpointRouting = false);
services.AddSubstructure(Configuration, Environment, SportFacilityUnitSettings);
services.AddApplication();
services.AddScoped<IPasswordHasher<User>, PasswordHasher<User>>();
var appSettingsSection = Configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsSection);
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
services.AddHttpContextAccessor();
_services = services;
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseCors(
options => options.SetIsOriginAllowed(x => _ = true).AllowAnyMethod().AllowAnyHeader().AllowCredentials()
);
app.UseMvc();
app.UseHsts();
app.UseMiddleware<JwtMiddleware>();
app.UseAuthentication();
app.UseRouting();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
When I hit the login button, which purpose is to send the request, I receive following error in web browser console.
Access to XMLHttpRequest at 'https://localhost:44316/api/User/Authenticate' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
The weirdest thing about it, is when I debug it, and set up a breakpoint in Api layer, debugger hits it, then it enters the service layer and fails somewhere inside Authentication method .
Go to IIS where your application is hosted and check if you have set the below information right.
#Step 1 : IIS --> HTTP Response header]
#Step 2 : : Setting 4 fields in your API app hosted under IIS
#Step 3: If the above 2 steps does not work, make sure you follow the msdn information to enable cors for your application
https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-3.1
Step 4 : : Investigate the header information you are using in your web API and if that's allowed under your IIS setting (as mentioned in Step 1)
Step 5 : : Place a breakpoint in your authenticate method to see where and why its failing. You may get more clue from this error information as well.
Step 6 : Try enabling CrossDomain to true from your front end.
Step 7 : Try enabling https for both the application (calling application and called application)

Blazor not receiving signalr created on server side

Following many tutorials, examples, this example below I call on the server side, but the client side does not receive, sometimes it works but sometimes it doesn’t (more doesn’t work than it works)
It was supposed to be very simple, but it's not, any suggest will help me so much!
Server side
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.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
var connection = #"data source=comandai.database.windows.net;initial catalog=HojeTaPago;persist security info=True;user id=Comandai;password=Ck#21112009;MultipleActiveResultSets=True;";
services.AddDbContext<ComandaiContext>(options => options.UseSqlServer(connection));
services.AddSignalR(options => options.KeepAliveInterval = TimeSpan.FromSeconds(5));
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "HojeTaPago API", Version = "v1" });
c.AddSecurityDefinition("basic", new OpenApiSecurityScheme
{
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "basic",
In = ParameterLocation.Header,
Description = "Basic Authorization header using the Bearer scheme."
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "basic"
}
},
new string[] {}
}
});
});
services.AddCors(options => options.AddPolicy("CorsPolicy",
builder =>
{
builder.AllowAnyMethod().AllowAnyHeader()
.AllowCredentials();
}));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseResponseCompression();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "HojeTaPago API V1");
c.RoutePrefix = string.Empty;
});
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<AuthenticationMiddleware>();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<NovoPedidoHub>("/novopedidohub");
endpoints.MapControllers();
});
}
}
Where im using the signalr
await _novoPedidoContext.Clients.All.SendAsync("NovoPedido", ListaComandaItem);
Client side - Blazor
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.AddRazorPages();
services.AddServerSideBlazor();
services.AddBlazoredLocalStorage();
services.AddBootstrapCss();
services.AddTransient<HubConnectionBuilder>();
}
// 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();
}
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.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
Where i call..
protected override async Task OnInitializedAsync()
{
DataService dataService = new DataService();
PedidosParaAceitar = new List<Comanda>(await dataService.BuscarComandasAbertas());
connection = _hubConnectionBuilder.WithUrl(dataService.servidor + "novopedidohub",
opt =>
{
opt.Transports = HttpTransportType.WebSockets;
opt.SkipNegotiation = true;
}).Build();
connection.On<List<ComandaItem>>("NovoPedido", async lista =>
{
var idEstabelecimento = await localStorage.GetItemAsync<int>("IdEstabelecimento");
if (lista.FirstOrDefault().Comanda.IdEstabelecimento == idEstabelecimento)
{
if (PedidosParaAceitar == null)
PedidosParaAceitar = new List<Comanda>();
if (PedidosParaAceitar.Count(x => x.Id == lista.FirstOrDefault().IdComanda) > 0)
foreach (var comandaitem in lista)
{
PedidosParaAceitar.FirstOrDefault(x => x.Id == lista.FirstOrDefault().IdComanda).ComandaItem.Add(comandaitem);
}
else
PedidosParaAceitar.Add(await dataService.BuscarComandaAberta(lista.FirstOrDefault().IdComanda));
StateHasChanged();
}
});
await connection.StartAsync();
}
You didn't specify in the tags if this was client-side (WASM) or server-side Blazor.
Looking at the question I noticed this line in ConfigureServices:
services.AddServerSideBlazor();
So you're attempting to use SignalR, a client-side communication library from the Server. In server-side Blazor all the C# code runs on the server. In this respect, SignalR is redundant since it's already being used by Blazor for communicating between the clients and the server.
By a very fortunate coincidence, I actually wrote an app to test this out recently. I created a server-side Blazor app, and wrote this service:
public class TalkService
{
public TalkService()
{
history = new List<string>();
}
public Action<string> OnChange { get; set; }
// inform all users of new message
public Task SendAsync(string message)
{
// add to history
history.Add(message);
// ensure only last 10 shown
if (history.Count > 10) history.RemoveAt(0);
OnChange.Invoke(message);
return Task.FromResult(0);
}
private readonly List<string> history;
public IReadOnlyList<string> GetHistory() => history;
}
I then registered it as a Singleton on the server (all clients use the same service)
in Startup.cs in the ConfigureServices() method:
services.AddSingleton<TalkService>();
Then rewrote Index.razor as follows:
#page "/"
#inject TalkService service
<p>Talk App started</p>
<p>Send a message: <input type="text"#bind="#message" />
<button class="btn btn-sm btn-primary" #onclick="Send" >Send</button>
</p>
#foreach (var m in messages)
{
<p>#m</p>
}
#code {
string message;
async Task Send()
{
if(!string.IsNullOrWhiteSpace(message))
await service.SendAsync(message);
message = string.Empty;
}
List<string> messages;
protected override void OnParametersSet()
{
// load history
messages = service.GetHistory().ToList();
// register for updates
service.OnChange += ChangeHandler;
}
protected void ChangeHandler(string message)
{
messages.Add(message);
InvokeAsync(StateHasChanged);
}
}
The talk service is a basic "chat" example of course. It's a Singleton so all pages/clients that reference it use the same instance. The service has a simple event OnChange that clients (like the Index page) can listen to for changes elsewhere.
SignalR isn't needed for this app since it's already "there" for server-side.
Demo App
The demo app also has a background service that generates time messages as well. I've pushed this to GitHub to help as a guide:
https://github.com/conficient/BlazorServerWithSignalR

ASP.NET core Web API Authorize Attribute return 404 Error & force redirect

I have asp.net core 2.0 solution which contains following projects:
Data: a class library for EF code
OAuth: as web application project for IdentityServer4 code
Api: for my API created as empty web project
Now the OAuth project is configured with aspnetidentity and its working fine, I able to get the token after authentication, and this is the startup code for it:
public void ConfigureServices(IServiceCollection services)
{
// connect with normal database
services.AddDbContext<MCareContext>(options => options.UseSqlServer(Configuration
.GetConnectionString("MCareConnection")));
services.AddIdentity<User, IdentityRole>()
.AddEntityFrameworkStores<MCareContext>()
.AddDefaultTokenProviders();
services.AddMvc();
// IS configurations database
var dbConnectionString = Configuration.GetConnectionString("MCareConnection.OAuth");
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
services.AddIdentityServer()
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(dbConnectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(dbConnectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
})
.AddAspNetIdentity<User>()
.AddDeveloperSigningCredential();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
DatabaseInitializer.InitializeDatabase(app);
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseIdentityServer();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
Now the problem on the API project, whenever I open any authorized controller/action its give me 404 error, and this the startup code:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MCareContext>(options => options.UseSqlServer(Configuration
.GetConnectionString("MCareConnection")));
services.AddIdentity<User, IdentityRole>()
.AddEntityFrameworkStores<MCareContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:60415";
options.ApiName = "mCareApi";
options.RequireHttpsMetadata = false;
});
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
app.UseAuthentication();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
If I close this part on the above code:
//services.AddIdentity<User, IdentityRole>()
//.AddEntityFrameworkStores<MCareContext>()
//.AddDefaultTokenProviders();
Then the Authorize attribute work as expected, but another new problem, it shows 500 internal server error, whenever I opened any controller like AccountController which expect UserManager userManager on its constructor
InvalidOperationException: Unable to resolve service for type
Microsoft.AspNetCore.Identity.UserManager`1[MCare.Data.Entities.User]
while attempting to activate MCare.Api.Controllers.AccountsController
I want to know why this is happening and how to solve this problem, without mixing IdentityServer4 project with the API project, because all the projects I have seen they mix both of them on one project.
Is it possible that DefaultChallengeScheme is redirecting to a page that does not exist such as a login page when it encounters the authorize attribute which could cause the 404?
Try setting the default challenge to the Jwt schema which returns not authorized.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:60415";
options.ApiName = "mCareApi";
options.RequireHttpsMetadata = false;
});
Or you could try the method I mentioned in the article below by providing a handler for the event.
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.JwtBearerEvents = new JwtBearerEvents
{
OnChallenge = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
}
};
options.Authority = "http://localhost:60415";
options.ApiName = "mCareApi";
options.RequireHttpsMetadata = false;
});
maybe you have to add the AddAuthentication line after the AddControllers() line

Resources