I updated ASP.NET 5 framework beta-8 packages with RC ones on previously working application. After I got it running next error occured in the startup process:
InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic
Microsoft.AspNet.Http.Authentication.Internal.DefaultAuthenticationManager.d__12.MoveNext()
var defaultPolicy =
new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
services.AddMvc(setup =>
{
setup.Filters.Add(new AuthorizeFilter(defaultPolicy)); // Error occurs here
});
If anyone had similar problem, I'd appreciate your idea or solution on what might have gone wrong. Explanation of this exception is also appreciated.
Startup.cs
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNet.Authorization;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.PlatformAbstractions;
using SuperUserMVC.Configuration;
using SuperUserMVC.Extensions;
using SuperUserMVC.GlobalModules;
using System;
namespace SuperUserMVC
{
public class Startup
{
public IConfigurationRoot Configuration { get; set; }
// Entry point for the application.
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
var builder = new ConfigurationBuilder()
.SetBasePath(appEnv.ApplicationBasePath)
.AddJsonFile("appsettings.json");
Configuration = builder.Build();
}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettingsBase>(Configuration.GetSection("AppSettingsBase"));
services.Configure<ConnectionString>(Configuration.GetSection("ConnectionString"));
services.AddSqlServerCache(cache =>
{
cache.ConnectionString = Configuration.Get<string>("ASPState:ConnectionString");
cache.SchemaName = Configuration.Get<string>("ASPState:Schema");
cache.TableName = Configuration.Get<string>("ASPState:Table");
});
services.AddSession(session =>
{
session.IdleTimeout = TimeSpan.FromMinutes(120);
});
// Only allow authenticated users.
var defaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
// Add MVC services to the services container.
services.AddMvc(setup =>
{
setup.Filters.Add(new AuthorizeFilter(defaultPolicy));
});
var builder = new ContainerBuilder();
builder.RegisterModule(new AutofacModule());
builder.Populate(services);
var container = builder.Build();
return container.Resolve<IServiceProvider>();
}
public void Configure(IApplicationBuilder app, IHttpContextAccessor httpContextAccessor)
{
// Catch unhandled exception in pipeline.
bool isProductionEnvironment = Configuration.Get<bool>("environmentVariables:isProductionEnvironment");
app.UseCustomUnhandledException(isProductionEnvironment, Configuration.Get<string>("defaultErrorPagePath"));
// Log requests.
app.UseVisitLogger(isProductionEnvironment);
// Session must be used before MVC routes.
app.UseSession();
// Configure the HTTP request pipeline.
app.UseCookieAuthentication(options =>
{
options.AuthenticationScheme = "Cookies";
options.LoginPath = new PathString("/Account/Login/");
options.AccessDeniedPath = new PathString("/Account/Forbidden/");
options.CookieName = "MyCookie";
options.AutomaticAuthenticate = true;
options.SessionStore = new MemoryCacheSessionStore();
});
AutoMapperInitializer.Init();
app.UseStaticFiles();
// Route configuration.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "AreaDefault",
template: "{area:exists=Demo}/{controller=Home}/{action=Index}/{id?}"
);
routes.MapRoute(
name: "Default",
template: "{controller=Home}/{action=Index}/{id?}"
);
});
}
}
}
Hopefully this will help somebody else because I just spent a lot of time dealing with this error even though I had set AutomaticChallenge = true.
Turns out you will get the same error if you put app.UseIdentity(); after app.UseMvc(routes => ...). Now that I know the answer it's obvious. It's because all this middleware happens in the order you add it.
This causes the "No authentication handler is configured" error:
public void Configure(...)
{
app.UseMvc(routes => { routes.MapRoute(...) }; );
app.UseIdentity();
}
This does not cause the error:
public void Configure(...)
{
app.UseIdentity();
app.UseMvc(routes => { routes.MapRoute(...); });
}
Try setting options.AutomaticChallenge = true; in your cookies options and it should work.
options.AutomaticAuthentication was split into options.AutomaticAuthenticate and options.AutomaticChallenge. If the last one is left to false, an exception is thrown because no authentication middleware handles the challenge applied by the authorization filter.
Put this on Configure method.
app.UseIdentity();
The problem was solved for me by making sure the cookies scheme was consistently named wherever it was referenced. e.g.:
public void ConfigureServices(IServiceCollection services)
{
// if using IdentityServer4
var builder = services.AddIdentityServer(options =>
{
options.AuthenticationOptions.AuthenticationScheme = Constants.DefaultCookieAuthenticationScheme;
...
})
services.AddIdentity<MyUser, IdentityRole>(options =>
{
options.Cookies.ApplicationCookie.AuthenticationScheme = Constants.DefaultCookieAuthenticationScheme;
...
}
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = Constants.DefaultCookieAuthenticationScheme,
AutomaticAuthenticate = false,
AutomaticChallenge = true
});
}
And when interacting with the authentication middleware. e.g.:
await HttpContext.Authentication.SignInAsync(Constants.DefaultCookieAuthenticationScheme, cp);
If you use app.UseIdentity(); and some other login middleware such as UseFacebookAuthentication make sure app.UseFacebookAuthentication() is AFTER app.UseIdentity();.
another possibility is missing the following setting in Configure
app.UseCookieAuthentication();
While it's tempting to place much of our configuration settings within the startup.cs file, it seems that the preferred way of doing things is to set your app.UseCookieAuthentication() - sans options - within the startup.cs file, and then place all of the 'options' and other details within a separate file.
Sort of like what we were doing with how the Global.asax file had pointers to the App_Start folder files in Asp.Net vBefore.
I suffered similar pain while trying to configure EF/Sql in the startup.cs, and by moving all 'options' outside of startup.cs things worked much better.
ALSO: take note of the Fredy Wenger comment to your question that points out the 'renaming' of many of the namespaces from v -8beta to v -RC1-final.
Related
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)
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
I wanna get value of a key in resource (.resx) file in .net core API. I followed the marked answer and implemented in my code. But When I am fetching value by key then only Key name is coming.
The linked is below which I followed :
How to get the .resx file strings in asp.net core
Please help me for this.
Thanks
Here is a working example , you could refer to :
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
var connection = #"Server=(localdb)\mssqllocaldb;Database=WebAPIDbContext;Trusted_Connection=True;ConnectRetryCount=0";
services.AddDbContext<RestaurantContext>(options => options.UseSqlServer(connection));
services.AddLocalization(o => o.ResourcesPath = "Resources");
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
//Request Localization
//var options = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
//app.UseRequestLocalization(options.Value);
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("zh-cn"),
};
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
// Formatting numbers, dates, etc.
SupportedCultures = supportedCultures,
// UI strings that we have localized.
SupportedUICultures = supportedCultures
});
app.UseHttpsRedirection();
app.UseMvc();
}
Controller
private readonly IStringLocalizer<SharedResource> _sharedLocalizer;
public ValuesController(IStringLocalizer<SharedResource> sharedLocalizer)
{
_sharedLocalizer = sharedLocalizer;
}
// GET api/values/5
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
var value = _sharedLocalizer["Title"];
return "value";
}
Resource file path
Note : the namespace of SharedResource.cs should be the root of application
namespace RestaurantReviewApi
{
public class SharedResource
{
}
}
Request url : https://localhost:44318/api/values/3?culture=zh-cn
The screenshot of result :
Reference : https://learn.microsoft.com/en-us/aspnet/core/fundamentals/localization?view=aspnetcore-2.2
I am currently at this page of the IdentityServer 4 guide http://docs.identityserver.io/en/dev/quickstarts/3_interactive_login.html and I am trying to start the MVC application.
However, I keep getting this error when I start my Client application
InvalidOperationException: Unable to resolve service for type 'IdentityServer4.Services.IIdentityServerInteractionService' while attempting to activate 'IdentityServer4.Quickstart.UI.HomeController'.
I went into the IdentityServer4 GitHub and copied the code from there but it doesn't run at all.
I am not sure how to proceed from here.
This is my Startup.cs
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace IdentityServerClient
{
public class Startup
{
// 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.AddMvc();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.SaveTokens = true;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseAuthentication();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
}
}
I can't get to the login page shown on the documentation as well.
If you're using the quickstart UI, you should be using the guide on it, here:
https://github.com/IdentityServer/IdentityServer4.Quickstart.UI
To quote that page:
Next you need to configure the authentication handlers:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// some details omitted
services.AddIdentityServer();
services.AddAuthentication()
...
You're missing:
services.AddIdentityServer()
.AddInMemoryCaching()
.AddClientStore<InMemoryClientStore>()
.AddResourceStore<InMemoryResourcesStore>(); // <-- Add this
As a result, none of the identity server services are registered to the dependency injection container, which is why you're seeing that error.
Looks like the tutorial documentation you linked to is out of date.
--
Here is a complete set of steps to follow:
dotnet new sln -n HelloID4
dotnet new mvc -n HelloID4
dotnet sln add HelloID4/HelloID4.csproj
cd HelloID4
git clone --depth 1 https://github.com/IdentityServer/IdentityServer4.Quickstart.UI
cp -r IdentityServer4.Quickstart.UI/* .
dotnet add package IdentityServer4
rm -r Controllers/
dotnet watch run
Now modify your Startup.cs to look like this:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
}); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddIdentityServer()
.AddInMemoryCaching()
.AddClientStore<InMemoryClientStore>()
.AddResourceStore<InMemoryResourcesStore>();
}
UPDATE
*Review this code : it may help, because i am also learning on it, and its working for me. *
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddTestUsers(Config.GetUsers())
//.AddInMemoryClients(Config.GetClients())
// this adds the config data from DB (clients, resources)
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(Configuration.GetConnectionString("IdentityConnectionString"), sql =>
sql.MigrationsAssembly(migrationsAssembly));
})
// this adds the operational data from DB (codes, tokens, consents)
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(Configuration.GetConnectionString("IdentityConnectionString"),
sql => sql.MigrationsAssembly(migrationsAssembly));
// this enables automatic token cleanup. this is optional.
options.EnableTokenCleanup = true;
options.TokenCleanupInterval = 30;
});
//.AddInMemoryIdentityResources(Config.GetIdentityResources())
//.AddInMemoryApiResources(Config.GetApiResources())
services.AddAuthentication();
}
// clients want to access resources (aka scopes)
public static IEnumerable<Client> GetClients()
{
// client credentials client
return new List<Client>
{
new Client
{
ClientId = "client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "api1" }
},
// resource owner password grant client
new Client
{
ClientId = "ro.client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "api1" }
},
// OpenID Connect hybrid flow and client credentials client (MVC)
};
}
if you are not using in-memory service
If you are getting this error : Unable to resolve service for type 'IdentityServer4.Stores.IClientStore'
Register the stores and implementation explicitly : ( give a try )
services.AddScoped<IUserStore<User>, UserService>();
services.AddScoped<IClientStore, ClientService>();
services.AddScoped<IScopeStore, ScopeService>();
services.AddScoped<IPersistedGrantStore, GrantService>();
I have a .NET Core web app which has an API. I've defined an Middleware class based on this answer like so:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
private readonly ILogger logger;
public ErrorHandlingMiddleware(RequestDelegate next,
ILoggerFactory loggerFactory)
{
this.next = next;
logger = loggerFactory.CreateLogger<ErrorHandlingMiddleware>();
}
public async Task Invoke(HttpContext context)
{
try
{
await next(context);
}
catch (Exception ex)
{
logger.LogError(0, ex, "An unhandled exception has occurred: " + ex.StackTrace);
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var code = HttpStatusCode.InternalServerError;
var message = exception.Message;
if (exception is BadRequestException)
{
code = HttpStatusCode.BadRequest;
}
else if (exception is NotFoundException)
{
code = HttpStatusCode.NotFound;
}
else if (exception is NotAuthorizedException)
{
code = HttpStatusCode.Forbidden;
}
else if (exception is NotAuthenticatedException)
{
code = HttpStatusCode.Unauthorized;
}
else
{
message = "An unexpected error occurred.";
}
var result = JsonConvert.SerializeObject(new { error = message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
}
The error handling only handles when an exception is thrown in code. A bad route does not throw an exception. The problem is that if I try to access a non-existent API route - that is, one that follows the API route convention and starts with "/api/adfasdf" - the API returns HTML (or the error page or the home page, I forget).
I've received some suggestions to check the context.Response.StatusCode after await next(context); executes, but it's 200.
How can I configure my web app such that it recognizes a bad API route and returns a 404?
UPDATE
Here is where/when I load the middleware in my Startup class:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime appLifetime, IOptions<OidcConfig> oidcConfigOptions)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
// Add Serilog to the logging pipeline
loggerFactory.AddSerilog();
app.UseMiddleware<ErrorHandlingMiddleware>();
if (env.IsLocal())
{
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true
});
}
var oidcConfig = oidcConfigOptions.Value;
// Configure the app to use Jwt Bearer Authentication
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Authority = oidcConfig.GetAuthority(),
Audience = oidcConfig.ResourceAppId,
TokenValidationParameters = new TokenValidationParameters
{
RequireExpirationTime = true,
RequireSignedTokens = true,
ValidateAudience = true,
ValidIssuer = oidcConfig.GetIssuer(),
ValidateIssuer = true,
ValidateActor = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true
},
});
app.UseSiteIdClaimInjection();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
appLifetime.ApplicationStopped.Register(() => this.ApplicationContainer.Dispose());
}
For posterity, the reason I was getting a 200 as #Nkosi helped uncover had to do with an MVC route definition in the Startup class. This came in automatically from https://github.com/aspnet/JavaScriptServices.
The solution was to change my route config to the following:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.MapWhen(x => !x.Request.Path.Value.StartsWith("/api"), builder =>
{
builder.UseMvc(routes =>
{
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
});
Referencing ASP.NET Core Middleware Fundamentals - Ordering
The order that middleware components are added in the Configure method
defines the order in which they are invoked on requests, and the
reverse order for the response. This ordering is critical for
security, performance, and functionality.
The Configure method (shown below) adds the following middleware
components:
Exception/error handling
Static file server
Authentication
MVC
C#
public void Configure(IApplicationBuilder app)
{
app.UseExceptionHandler("/Home/Error"); // Call first to catch exceptions
// thrown in the following middleware.
app.UseStaticFiles(); // Return static files and end pipeline.
app.UseIdentity(); // Authenticate before you access
// secure resources.
app.UseMvcWithDefaultRoute(); // Add MVC to the request pipeline.
}
In the code above, UseExceptionHandler is the first middleware
component added to the pipeline—therefore, it catches any exceptions
that occur in later calls.
Based on code provided in OP and quoted documentation I would suggest adding your exception earlier or first to the pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime appLifetime) {
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
loggerFactory.AddSerilog();
app.UseMiddleware<ErrorHandlingMiddleware>(); // Call first to catch exceptions
// thrown in the following middleware.
if (env.IsLocal()) {
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions { HotModuleReplacement = true });
}
//Bunch of other stuff
}
UPDATE Based on comments.
I suspect that one of the middleware further down the pipeline is causing this issue. try removing them one by one and checking if you get the same behavior in order to narrow down which one is the culprit.
Similar to above answer, We are using this in our Angular and ASP.NET MVC Core project:
public virtual void Configure(IHostingEnvironment environment, IApplicationBuilder app)
{
// configurations...
app.UseMvc(routes =>
{
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
app.MapWhen(o => !o.Request.Path.Value.StartsWith("/api"), builder =>
{
builder.UseMvc(routes =>
{
routes.MapRoute("spa-fallback", "{*anything}", new { controller = "Home", action = "Index" });
});
});
}