following problem I can create a token but when I use it the authentication failed, I try to copy from an example but I had problems because
some methods are not "there". (like principal.SetScopes, but it seems to be exist in the Github repository and in other examples)
The only error I get is the failure of the AuthorizationFilter.
Here the method for creating the token
[HttpPost("~/connect/token"), Produces("application/json")]
public async Task<IActionResult> Exchange(OpenIdConnectRequest connectRequest)
{
if (connectRequest.IsPasswordGrantType())
{
var user = await _userManager.FindByNameAsync(connectRequest.Username);
if (user == null)
{
return Forbid(
authenticationSchemes: OpenIddictServerDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIdConnectConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIdConnectConstants.Properties.ErrorDescription] = "The username/password couple is invalid."
}));
}
var result = await _signInManager.CheckPasswordSignInAsync(user, connectRequest.Password, lockoutOnFailure: true);
if (!result.Succeeded)
{
return Forbid(
authenticationSchemes: OpenIddictValidationDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIdConnectConstants.Properties.Error] = Errors.InvalidGrant,
[OpenIdConnectConstants.Properties.ErrorDescription] = "The username/password couple is invalid."
}));
}
var principal = await _signInManager.CreateUserPrincipalAsync(user);
//principal.SetScopes(new[]
//{
// Scopes.OpenId,
// Scopes.Email,
// Scopes.Profile,
// Scopes.Roles
//}.Intersect(connectRequest.GetScopes()));
//foreach (var claim in principal.Claims)
//{
// claim.SetDestinations(GetDestinations(claim, principal));
//}
var sign = SignIn(principal, OpenIddictServerDefaults.AuthenticationScheme);
return sign;
}
throw new Exception("Not supported");
}
Here is my startup
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DocumentiveContext>((provider, builder) =>
{
var configuration = provider.GetService<IConfiguration>();
builder.UseSqlServer(configuration.GetConnectionString("connectionString"));
builder.UseOpenIddict();
});
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
})
.AddEntityFrameworkStores<DocumentiveContext>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options =>
{
options.Events.OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = Claims.Name;
options.ClaimsIdentity.UserIdClaimType = Claims.Subject;
options.ClaimsIdentity.RoleClaimType = Claims.Role;
});
services.AddAuthentication(options =>
options.DefaultScheme = OpenIddictValidationDefaults.AuthenticationScheme);
services.AddOpenIddict(builder =>
{
builder.AddCore(coreBuilder => coreBuilder.UseEntityFrameworkCore().UseDbContext<DocumentiveContext>());
builder.AddServer(serverBuilder =>
{
serverBuilder.UseMvc();
//serverBuilder.EnableTokenEndpoint("/connect/token");
serverBuilder.EnableAuthorizationEndpoint("/connect/authorize")
.EnableLogoutEndpoint("/connect/logout")
.EnableTokenEndpoint("/connect/token")
.EnableUserinfoEndpoint("/connect/userinfo")
.EnableUserinfoEndpoint("/connect/verify");
serverBuilder.AllowPasswordFlow();
serverBuilder.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles, "demo_api");
serverBuilder.AddDevelopmentSigningCertificate();
serverBuilder.AcceptAnonymousClients();
serverBuilder.DisableHttpsRequirement();
});
builder.AddValidation(validationBuilder =>
{
});
});
services.AddMvc(options => options.EnableEndpointRouting = false);
services.AddGrpc();
services.AddTransient<IUserInformationService, UserInformationService>();
}
// 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.UseRouting();
app.UseAuthentication();
app.UseStaticFiles();
app.UseSerilogRequestLogging();
app.UseAuthorization();
app.UseMvcWithDefaultRoute();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GreeterService>();
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
});
});
}
}
Any idea?
After updating to the newest Version 3 of Openiddict, it works.
Also I needed to set the Authentication Scheme in the attribute.
[Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)]
Related
I need to add a Basic Authentication to a set of API.
DISCLAIMER: The APIs are inside an intranet and contains public data. They can be consumed from external users only through an API gateway where strong authentication and authorization are implemented. This Basic Authentication is needed only to avoid that internal development team calls directly this service. User and password are not the real one.
I've used the package ZNetCS.AspNetCore.Authentication.Basic, version 4.0.0
This is my startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services
.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme)
.AddBasicAuthentication(
options =>
{
options.Realm = "My Application";
options.Events = new BasicAuthenticationEvents
{
OnValidatePrincipal = context =>
{
if ((context.UserName.ToLower() == "gateway0107")
&& (context.Password == "jir6STt6437yMAQpl"))
{
var claims = new List<Claim>{
new Claim(ClaimTypes.Name,
context.UserName,
context.Options.ClaimsIssuer)
};
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(new ClaimsIdentity(
claims,
BasicAuthenticationDefaults.AuthenticationScheme)),
new Microsoft.AspNetCore.Authentication.AuthenticationProperties(),
BasicAuthenticationDefaults.AuthenticationScheme);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
return Task.FromResult(AuthenticateResult.Fail("Authentication failed."));
}
};
});
services.AddMvcCore();
services.AddApiVersioning(
options =>
{
options.ReportApiVersions = true;
});
services.AddVersionedApiExplorer(
options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
services.AddSwaggerGen(
options =>
{
options.OperationFilter<SwaggerDefaultValues>();
options.IncludeXmlComments(XmlCommentsFilePath);
});
services.AddCors(options =>
{
options.AddPolicy(name: "AllowAllOrigins",
builder =>
{
builder.AllowAnyOrigin();
});
});
services.AddControllers(options =>
{
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Point)));
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Coordinate)));
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(LineString)));
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(MultiLineString)));
}).AddNewtonsoftJson(options =>
{
foreach (var converter in NetTopologySuite.IO.GeoJsonSerializer.Create(new GeometryFactory(new PrecisionModel(), 4326)).Converters)
{
options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
options.SerializerSettings.Converters.Add(converter);
}
}).SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
{
app.UseForwardedHeaders();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "swagger-ui")),
RequestPath = "/swagger-ui"
});
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("AllowAllOrigins");
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseSwagger();
app.UseSwaggerUI(
options =>
{
// build a swagger endpoint for each discovered API version
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
}
});
}
static string XmlCommentsFilePath
{
get
{
var basePath = PlatformServices.Default.Application.ApplicationBasePath;
var fileName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name + ".xml";
return Path.Combine(basePath, fileName);
}
}
}
But testing in Postman and from SwaggerUI I get always 410 Unauthorized.
Setting a breakpoint inside the OnValidatePrincipal, it never gets hit.
Should I have to add something else?
I am building a website in dotnet core and have recently started using claims based authentication and authorization.
In a view component I am checking if the user has access to a policy.
public NavigationViewComponent(
IContextManager contextManager,
IAuthorizationService authorizationService)
{
_contextManager = contextManager;
_authorizationService = authorizationService;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var showAdmin = _contextManager.Principal != null &&
(await _authorizationService.AuthorizeAsync(_contextManager.Principal, "Admin")).Succeeded;
var vm = new NavigationViewModel
{
ShowAdmin = showAdmin
};
return View(vm);
}
However, I am receiving the Exception InvalidOperationException: No policy found: Admin..
My startup.cs contains the following inside the ConfigureServices method:
services.AddAuthorization(options =>
{
options.AddPolicy("Admin",
policy => policy.Requirements.Add(new HasPermissionRequirement("ADMIN")));
});
What else do I need to configure in order to get this to work correctly?
For reference I am registering 3 additional IAuthorizationHandler implementations and 1 IAuthorizationPolicyProvider.
Edit
For reference, the whole startup.cs looks something similar to this.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
// Name and policy settings
options.Cookie.Name = "account";
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.HttpOnly = true;
// Sign the user out after 28 days of inactivity
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromDays(28);
// Challenge actions
options.LoginPath = new PathString("/account/login");
options.ReturnUrlParameter = "returnUrl";
});
services.AddAuthorization(options =>
{
options.AddPolicy("Admin",
policy => policy.Requirements.Add(new HasPermissionRequirement("ADMIN")));
});
services.AddSingleton<IAuthorizationHandler, HasPermissionHandler>();
services.AddSingleton<IAuthorizationHandler, StrategyAuthorizationCrudHandler>();
services.AddSingleton<IAuthorizationHandler, UserAuthorizationCrudHandler>();
services.AddSingleton<IAuthorizationPolicyProvider, HasPermissionPolicyProvider>();
// AddAntiforgery, AddSession,AddDistributedRedisCache and AddDataProtection omitted
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling =
ReferenceLoopHandling.Ignore;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
app.UseCookiePolicy(new CookiePolicyOptions
{
CheckConsentNeeded = httpContext => false,
MinimumSameSitePolicy = SameSiteMode.Strict,
Secure = CookieSecurePolicy.SameAsRequest
});
app.UseAuthentication();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseStaticFiles();
var supportedCultures = new[]
{
new CultureInfo("en-GB")
};
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture(supportedCultures[0]),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
});
app.UseSession();
app.UseMiddleware<UserMiddleware>();
app.UseMiddleware<LoggingMiddleware>();
app.UseMvc(routes =>
{
routes.MapRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
});
}
HasPermissionRequirement.cs
public class HasPermissionRequirement : IAuthorizationRequirement
{
public string Permission { get; private set; }
public HasPermissionRequirement(string permission)
{
Permission = permission;
}
}
HasPermissionHandler.cs
public class HasPermissionHandler : AuthorizationHandler<HasPermissionRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
HasPermissionRequirement requirement)
{
var hasPermission = context.User.HasClaim("Permission", requirement.Permission);
if (hasPermission)
context.Succeed(requirement);
return Task.CompletedTask;
}
}
HasPermissionPolicyProvider.cs
public class HasPermissionPolicyProvider : IAuthorizationPolicyProvider
{
private const string PolicyPrefix = "HasPermission";
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if (!policyName.StartsWith(PolicyPrefix, StringComparison.OrdinalIgnoreCase))
return Task.FromResult<AuthorizationPolicy>(null);
var permission = policyName.Substring(PolicyPrefix.Length);
var policy = new AuthorizationPolicyBuilder();
policy.AddRequirements(new HasPermissionRequirement(permission));
return Task.FromResult(policy.Build());
}
public Task<AuthorizationPolicy> GetDefaultPolicyAsync() =>
Task.FromResult(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
}
I've managed to solve this by taking a closer look at the Microsoft documentation after being pointed in the right direction.
https://github.com/aspnet/Docs/blob/master/aspnetcore/security/authorization/iauthorizationpolicyprovider.md#multiple-authorization-policy-providers
When using custom IAuthorizationPolicyProvider implementations, keep in mind that ASP.NET Core only uses one instance of IAuthorizationPolicyProvider. If a custom provider isn't able to provide authorization policies for all policy names, it should fall back to a backup provider.
As a result I changed the implementation of my HasPermissionPolicyProvider to CustomPolicyProvider and the content is below:
public class CustomPolicyProvider : DefaultAuthorizationPolicyProvider
{
private const string PermissionPolicyPrefix = "HasPermission";
public CustomPolicyProvider(IOptions<AuthorizationOptions> options) : base(options)
{
}
public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
var policy = await base.GetPolicyAsync(policyName);
if (policy != null) return policy;
if (policyName.StartsWith(PermissionPolicyPrefix, StringComparison.OrdinalIgnoreCase))
{
var permission = policyName.Substring(PermissionPolicyPrefix.Length);
return new AuthorizationPolicyBuilder()
.AddRequirements(new HasPermissionRequirement(permission))
.Build();
}
return null;
}
}
This means that you can only have one PolicyProvider which must handle all your logic. The change is to ensure that it calls the default implementation if you require multiple handler logic.
I've build a background task in my ASP.NET Core 2.1 following this tutorial: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1#consuming-a-scoped-service-in-a-background-task
Compiling gives me an error:
System.InvalidOperationException: 'Cannot consume scoped service 'MyDbContext' from singleton 'Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor'.'
What causes that error and how to fix it?
Background task:
internal class OnlineTaggerMS : IHostedService, IDisposable
{
private readonly CoordinatesHelper _coordinatesHelper;
private Timer _timer;
public IServiceProvider Services { get; }
public OnlineTaggerMS(IServiceProvider services, CoordinatesHelper coordinatesHelper)
{
Services = services;
_coordinatesHelper = coordinatesHelper;
}
public Task StartAsync(CancellationToken cancellationToken)
{
// Run every 30 sec
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(30));
return Task.CompletedTask;
}
private async void DoWork(object state)
{
using (var scope = Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
Console.WriteLine("Online tagger Service is Running");
// Run something
await ProcessCoords(dbContext);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
private async Task ProcessCoords(MyDbContext dbContext)
{
var topCoords = await _coordinatesHelper.GetTopCoordinates();
foreach (var coord in topCoords)
{
var user = await dbContext.Users.SingleOrDefaultAsync(c => c.Id == coord.UserId);
if (user != null)
{
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
//expire time = 120 sec
var coordTimeStamp = DateTimeOffset.FromUnixTimeMilliseconds(coord.TimeStamp).AddSeconds(120).ToUnixTimeMilliseconds();
if (coordTimeStamp < now && user.IsOnline == true)
{
user.IsOnline = false;
await dbContext.SaveChangesAsync();
}
else if (coordTimeStamp > now && user.IsOnline == false)
{
user.IsOnline = true;
await dbContext.SaveChangesAsync();
}
}
}
}
public void Dispose()
{
_timer?.Dispose();
}
}
Startup.cs:
services.AddHostedService<OnlineTaggerMS>();
Program.cs:
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<TutorDbContext>();
DbInitializer.Initialize(context);
}
catch(Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
}
}
host.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
Full startup.cs:
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.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
// ===== Add Identity ========
services.AddIdentity<User, IdentityRole>()
.AddEntityFrameworkStores<TutorDbContext>()
.AddDefaultTokenProviders();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // => remove default claims
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["JwtIssuer"],
ValidAudience = Configuration["JwtIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])),
ClockSkew = TimeSpan.Zero // remove delay of token when expire
};
});
//return 401 instead of redirect
services.ConfigureApplicationCookie(options =>
{
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
options.Events.OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
services.AddMvc();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Version = "v1", Title = "xyz", });
// Swagger 2.+ support
var security = new Dictionary<string, IEnumerable<string>>
{
{"Bearer", new string[] { }},
};
c.AddSecurityDefinition("Bearer", new ApiKeyScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"",
Name = "Authorization",
In = "header",
Type = "apiKey"
});
c.AddSecurityRequirement(security);
});
services.AddHostedService<OnlineTaggerMS>();
services.AddTransient<UsersHelper, UsersHelper>();
services.AddTransient<CoordinatesHelper, CoordinatesHelper>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IServiceProvider serviceProvider, IApplicationBuilder app, IHostingEnvironment env, TutorDbContext dbContext)
{
dbContext.Database.Migrate();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("v1/swagger.json", "xyz V1");
});
CreateRoles(serviceProvider).GetAwaiter().GetResult();
}
private async Task CreateRoles(IServiceProvider serviceProvider)
{
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var UserManager = serviceProvider.GetRequiredService<UserManager<User>>();
string[] roleNames = { "x", "y", "z", "a" };
IdentityResult roleResult;
foreach (var roleName in roleNames)
{
var roleExist = await RoleManager.RoleExistsAsync(roleName);
if (!roleExist)
{
roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName));
}
}
var _user = await UserManager.FindByEmailAsync("xxx");
if (_user == null)
{
var poweruser = new User
{
UserName = "xxx",
Email = "xxx",
FirstName = "xxx",
LastName = "xxx"
};
string adminPassword = "xxx";
var createPowerUser = await UserManager.CreateAsync(poweruser, adminPassword);
if (createPowerUser.Succeeded)
{
await UserManager.AddToRoleAsync(poweruser, "xxx");
}
}
}
You need to inject IServiceScopeFactory to generate a scope. Otherwise you are not able to resolve scoped services in a singleton.
using (var scope = serviceScopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetService<MyDbContext>();
}
Edit:
It's perfectly fine to just inject IServiceProvider and do the following:
using (var scope = serviceProvider.CreateScope()) // this will use `IServiceScopeFactory` internally
{
var context = scope.ServiceProvider.GetService<MyDbContext>();
}
The second way internally just resolves IServiceProviderScopeFactory and basically does the very same thing.
For Entity Framework DbContext classes there is a better way now with:
services.AddDbContextFactory<MyDbContext>(options => options.UseSqlServer(...))
Then you can just inject the factory in your singleton class like this:
IDbContextFactory<MyDbContext> myDbContextFactory
And finally use it like this:
using var myDbContex = _myDbContextFactory.CreateDbContext();
Though #peace answer worked for him, if you do have a DBContext in your IHostedService you need to use a IServiceScopeFactory.
To see an amazing example of how to do this check out this answer How should I inject a DbContext instance into an IHostedService?.
If you would like to read more about it from a blog, check this out .
I found the reason of an error. It was the CoordinatesHelper class, which is used in the the background task OnlineTaggerMS and is a Transient - so it resulted with an error. I have no idea why compiler kept throwing errors pointing at MyDbContext, keeping me off track for few hours.
You still need to register MyDbContext with the service provider. Usually this is done like so:
services.AddDbContext<MyDbContext>(options => {
// Your options here, usually:
options.UseSqlServer("YourConnectionStringHere");
});
If you also posted your Program.cs and Startup.cs files, it may shed some more light on things, as I was able to quickly setup a test project implementing the code and was unable to reproduce your issue.
I've build a background task in my ASP.NET Core 2.1 following this tutorial: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1#consuming-a-scoped-service-in-a-background-task
Compiling gives me an error:
System.InvalidOperationException: 'Cannot consume scoped service 'MyDbContext' from singleton 'Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor'.'
What causes that error and how to fix it?
Background task:
internal class OnlineTaggerMS : IHostedService, IDisposable
{
private readonly CoordinatesHelper _coordinatesHelper;
private Timer _timer;
public IServiceProvider Services { get; }
public OnlineTaggerMS(IServiceProvider services, CoordinatesHelper coordinatesHelper)
{
Services = services;
_coordinatesHelper = coordinatesHelper;
}
public Task StartAsync(CancellationToken cancellationToken)
{
// Run every 30 sec
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(30));
return Task.CompletedTask;
}
private async void DoWork(object state)
{
using (var scope = Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
Console.WriteLine("Online tagger Service is Running");
// Run something
await ProcessCoords(dbContext);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
private async Task ProcessCoords(MyDbContext dbContext)
{
var topCoords = await _coordinatesHelper.GetTopCoordinates();
foreach (var coord in topCoords)
{
var user = await dbContext.Users.SingleOrDefaultAsync(c => c.Id == coord.UserId);
if (user != null)
{
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
//expire time = 120 sec
var coordTimeStamp = DateTimeOffset.FromUnixTimeMilliseconds(coord.TimeStamp).AddSeconds(120).ToUnixTimeMilliseconds();
if (coordTimeStamp < now && user.IsOnline == true)
{
user.IsOnline = false;
await dbContext.SaveChangesAsync();
}
else if (coordTimeStamp > now && user.IsOnline == false)
{
user.IsOnline = true;
await dbContext.SaveChangesAsync();
}
}
}
}
public void Dispose()
{
_timer?.Dispose();
}
}
Startup.cs:
services.AddHostedService<OnlineTaggerMS>();
Program.cs:
public class Program
{
public static void Main(string[] args)
{
var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<TutorDbContext>();
DbInitializer.Initialize(context);
}
catch(Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
}
}
host.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
Full startup.cs:
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.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
// ===== Add Identity ========
services.AddIdentity<User, IdentityRole>()
.AddEntityFrameworkStores<TutorDbContext>()
.AddDefaultTokenProviders();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // => remove default claims
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["JwtIssuer"],
ValidAudience = Configuration["JwtIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])),
ClockSkew = TimeSpan.Zero // remove delay of token when expire
};
});
//return 401 instead of redirect
services.ConfigureApplicationCookie(options =>
{
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
options.Events.OnRedirectToAccessDenied = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
services.AddMvc();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Version = "v1", Title = "xyz", });
// Swagger 2.+ support
var security = new Dictionary<string, IEnumerable<string>>
{
{"Bearer", new string[] { }},
};
c.AddSecurityDefinition("Bearer", new ApiKeyScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"",
Name = "Authorization",
In = "header",
Type = "apiKey"
});
c.AddSecurityRequirement(security);
});
services.AddHostedService<OnlineTaggerMS>();
services.AddTransient<UsersHelper, UsersHelper>();
services.AddTransient<CoordinatesHelper, CoordinatesHelper>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IServiceProvider serviceProvider, IApplicationBuilder app, IHostingEnvironment env, TutorDbContext dbContext)
{
dbContext.Database.Migrate();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("v1/swagger.json", "xyz V1");
});
CreateRoles(serviceProvider).GetAwaiter().GetResult();
}
private async Task CreateRoles(IServiceProvider serviceProvider)
{
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var UserManager = serviceProvider.GetRequiredService<UserManager<User>>();
string[] roleNames = { "x", "y", "z", "a" };
IdentityResult roleResult;
foreach (var roleName in roleNames)
{
var roleExist = await RoleManager.RoleExistsAsync(roleName);
if (!roleExist)
{
roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName));
}
}
var _user = await UserManager.FindByEmailAsync("xxx");
if (_user == null)
{
var poweruser = new User
{
UserName = "xxx",
Email = "xxx",
FirstName = "xxx",
LastName = "xxx"
};
string adminPassword = "xxx";
var createPowerUser = await UserManager.CreateAsync(poweruser, adminPassword);
if (createPowerUser.Succeeded)
{
await UserManager.AddToRoleAsync(poweruser, "xxx");
}
}
}
You need to inject IServiceScopeFactory to generate a scope. Otherwise you are not able to resolve scoped services in a singleton.
using (var scope = serviceScopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetService<MyDbContext>();
}
Edit:
It's perfectly fine to just inject IServiceProvider and do the following:
using (var scope = serviceProvider.CreateScope()) // this will use `IServiceScopeFactory` internally
{
var context = scope.ServiceProvider.GetService<MyDbContext>();
}
The second way internally just resolves IServiceProviderScopeFactory and basically does the very same thing.
For Entity Framework DbContext classes there is a better way now with:
services.AddDbContextFactory<MyDbContext>(options => options.UseSqlServer(...))
Then you can just inject the factory in your singleton class like this:
IDbContextFactory<MyDbContext> myDbContextFactory
And finally use it like this:
using var myDbContex = _myDbContextFactory.CreateDbContext();
Though #peace answer worked for him, if you do have a DBContext in your IHostedService you need to use a IServiceScopeFactory.
To see an amazing example of how to do this check out this answer How should I inject a DbContext instance into an IHostedService?.
If you would like to read more about it from a blog, check this out .
I found the reason of an error. It was the CoordinatesHelper class, which is used in the the background task OnlineTaggerMS and is a Transient - so it resulted with an error. I have no idea why compiler kept throwing errors pointing at MyDbContext, keeping me off track for few hours.
You still need to register MyDbContext with the service provider. Usually this is done like so:
services.AddDbContext<MyDbContext>(options => {
// Your options here, usually:
options.UseSqlServer("YourConnectionStringHere");
});
If you also posted your Program.cs and Startup.cs files, it may shed some more light on things, as I was able to quickly setup a test project implementing the code and was unable to reproduce your issue.
I try to secure up my API with a cookie token.
Everything is working fine, i try to sign in i generate a cookie the cookie is set by browser, and then i try to request /auth/info2. The cookie is send but i got an 401 error.
Can u give me a hint? How to solve this problem?
Currently my code looks like that:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
{
//options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
options.UseInMemoryDatabase("som_tmp");
}
);
services.AddTransient<IEmailSender, EmailSender>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddIdentity<SomUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o =>
{
o.Cookie = new CookieBuilder()
{
HttpOnly = false,
Name = "som_session"
};
});
services.ConfigureApplicationCookie(options =>
{
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
services.AddAuthorization();
services.AddMvc();
services.AddOData();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ApplicationDbContext context, UserManager<SomUser> userManager, RoleManager<IdentityRole> roleManager)
{
var model = GetEdmModel(app.ApplicationServices);
app.UseDefaultFiles();
app.UseStaticFiles(new StaticFileOptions
{
ServeUnknownFileTypes = true
});
app.UseAuthentication();
app.UseMvc(routebuilder =>
{
routebuilder.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
routebuilder.MapODataServiceRoute("oData", "oData", model);
});
DbInitializer.Initialize(context, userManager, roleManager);
}
Controller:
[Authorize]
[HttpGet("info2")]
public async Task<JsonResult> Get2()
{
return Json("Info2");
//return Json( await GetCurrentUser() );
}
[AllowAnonymous]
[HttpPost("login2")]
public async Task<JsonResult> Login2([FromBody] LoginDto loginDto)
{
var user = await _userManager.FindByNameAsync(loginDto.Username);
if (user == null)
{
user = await _userManager.FindByEmailAsync(loginDto.Username);
}
if (user != null)
{
var passwordHasher = new PasswordHasher<SomUser>();
if (passwordHasher.VerifyHashedPassword(user, user.PasswordHash, loginDto.Password) == PasswordVerificationResult.Success)
{
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
return Json(true);
}
}
return Json(false);
}
i got it working with setting the DefaultScheme:
services.AddAuthentication(o =>
{
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
You will receive 401 atleast once since there a redirection to login involved.
second result should have 'true' as a output.