When testing the refresh token flow, I get the following error when using the overloaded signature that specifies a custom key for the UseOpenIddict method (in this case ).
InvalidOperationException: The entity type 'OpenIddictAuthorization' was not found. Ensure that the entity type has been added to the model.
What's interesting is that if I don't use the overloaded method to use int as the primary key, it works correctly and I receive the refresh token. It's only when I use the overload that I receive this error.
Here is the context declaration in startup
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<RouteManagerContext>(options =>
{
options.UseSqlServer(AppSettings.RouteManagerContext);
options.UseOpenIddict<int>();
});
services.AddIdentity<ApplicationUser, ApplicationRole>().AddEntityFrameworkStores<RouteManagerContext>().AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
});
services.AddOpenIddict(options =>
{
options.AddEntityFrameworkCoreStores<RouteManagerContext>();
options.AddMvcBinders();
options.EnableTokenEndpoint("/connect/token");
options.AllowPasswordFlow()
.AllowRefreshTokenFlow()
.SetAccessTokenLifetime(TimeSpan.FromMinutes(1))
.SetRefreshTokenLifetime(TimeSpan.FromMinutes(20160))
options.DisableHttpsRequirement();
});
services.AddAuthentication()
.AddOAuthValidation()
.AddFacebook(o => { o.ClientId = AppSettings.FacebookAppID; o.ClientSecret = AppSettings.FacebookAppSecret; });
services.AddDocumentation(AppSettings);
}
and here is my context
public class RouteManagerContext : IdentityDbContext<ApplicationUser, ApplicationRole, int>
{
public RouteManagerContext(DbContextOptions<RouteManagerContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
The application seems to be configured correctly as I have all the tables in the database that openiddict needs: Applications, Authorizations, Tokens, etc...
All the examples seem to be configured the same way.
Any ideas?
I got the same error but with Guig key options.UseOpenIddict<Guid>();
I use OpenIddict 2.0 for and the method services.AddOpenIddict<Guid>() dont exists
I solve the error, using this code:
services.AddOpenIddict()
.AddCore(options =>
{
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>()
.ReplaceDefaultEntities<Guid>();
})
.AddServer(options =>...) //here more options
I also use Guid for key of Identity entities
public class ApplicationDbContext : IdentityDbContext<IdentityUser<Guid>, IdentityRole<Guid>, Guid>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
By calling options.UseOpenIddict<int>();, you're asking Entity Framework Core to use the default OpenIddict entities but with a custom key type (int instead of string).
Yet, you're also using services.AddOpenIddict(), that configures OpenIddict with the default entities and the default key type. When the Entity Framework Core stores are called by OpenIddict, the expected entities can't be found in the context since their generic definition is different.
To fix the inconsistency, use services.AddOpenIddict<int>().
Related
I created a new asp.net core web application which uses individual user accounts. now i am trying to implement a simple role assignment scenario.
so i register a test user, where the user got added inside the AspNetUser table:-
then i add a new Role named "Administrator" inside the AspNetRole:-
then i added a new AspNetUserRole to link the user to the Role:-
then i added the following Authorize annotation on the About action method:-
[Authorize(Roles = "Administrator")]
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}
but when i try to access the About action method using the user, i got this error:-
You do not have access to this resource."
EDIT
Here is the startup.cs , which i have not modified, so i think it contain the built-in code:-
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using WebApplication2.Data;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace WebApplication2
{
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.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.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// 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();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
I guess you manually create role and link role in AspNetUserRoletable after creating your user . Please don't forget to Logout user and login again , so role claims will get/update the new added role .
Your identity service is not configured for roles. AddDefaultIdentity cannot handle roles. You need AddIdentity
Instead of:
services.AddDefaultIdentity<IdentityUser>().AddEntityFrameworkStores<ApplicationDbContext>();
Try:
services.AddIdentity<IdentityUser, IdentityRole>().AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
Short answer
Add IdentityRole :
services.AddDefaultIdentity<IdentityUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
Long Answer
For properly using of roles/policies, you need to follow the below steps:
configure ApplicationDbContext for using IdentityRole
configure Identity service to use IdentityRole
configure application cookie
define authorization policies
configure authorization for razor pages
Notice : if you are using razor pages, Authorization attributes must be applied to the PageModel model not the actions
before proceeding with the solution, it is worth to mention that it is a best practice to use custom user and role models instead of IdentityUser and IdentityModel. This will help you add custom fields to the user and role easily.
So, first lets create our custom user and role models:
public class AppUser : IdentityUser
{
//custom fields can be defined here
}
public class AppRole : IdentityRole
{
//custom fields can be defined here
}
public class AppUserRole : IdentityUserRole<string>
{
public virtual AppUser User { get; set; }
public virtual AppRole Role { get; set; }
}
Now we can start with configuring ApplicationDbContext:
public class ApplicationDbContext : IdentityDbContext<AppUser, AppRole, string, IdentityUserClaim<string>, AppUserRole, IdentityUserLogin<string>, IdentityRoleClaim<string>, IdentityUserToken<string>>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
// AppUserRole relationship solution from so
// https://stackoverflow.com/questions/51004516/net-core-2-1-identity-get-all-users-with-their-associated-roles/51005445#51005445
builder.Entity<AppUserRole>(userRole =>
{
userRole.HasKey(ur => new { ur.UserId, ur.RoleId });
userRole.HasOne(ur => ur.Role)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
userRole.HasOne(ur => ur.User)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
}
}
}
configuring Identity
services.AddIdentity<AppUser, AppRole>(ops =>
{
ops.SignIn.RequireConfirmedEmail = true;
// Lockout settings
ops.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
ops.Lockout.MaxFailedAccessAttempts = 9;
ops.Lockout.AllowedForNewUsers = true;
// User settings
ops.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
configure application cookie
services.ConfigureApplicationCookie(ops =>
{
// Cookie settings
ops.Cookie.HttpOnly = false;
ops.ExpireTimeSpan = TimeSpan.FromMinutes(30);
// If the LoginPath isn't set, ASP.NET Core defaults the path to /Account/Login.
ops.LoginPath = $"/Identity/Account/Login";
// If the AccessDeniedPath isn't set, ASP.NET Core defaults the path to /Account/AccessDenied.
ops.AccessDeniedPath = $"/Identity/Account/AccessDenied";
ops.SlidingExpiration = true;
});
define authorization policies
services.AddAuthorization(ops =>
{
ops.AddPolicy("Administrator", policy =>
{
policy.RequireRole("Administrator");
});
});
Now it is possible to use roles/policies in different ways:
1- define authorization policies in startup
services.AddMvc()
.AddRazorPagesOptions(ops =>
{
ops.Conventions.AuthorizeFolder("/", "Administrator");
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
2- apply authorization attributes on actions in case of MVC
[Authorize(Roles = "Administrator")]
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}
3- or apply policy on PageModel for Razor Pages
[Authorize(Policy = "Administrator")]
public class AboutModel : PageModel
{
//-----
}
[UPDATE]
following to your comment below;
Let's consider that you will develop a news website management panel; basically you will need roles like Admins to manage the site settings and Authors to post the news pages, and probably Managers to approve the posted news. With this scenario you can survive with the default Identity settings and role based authorization.
But for example; if you need to allow only authors with more than 100 posted articles and are older than 25 to be able to approve their posts without the Managers approval then you need to customize the IdentityUser and use policy/claim based authorization, in this case the long answer will help you more to develop the application.
you can read more about authorization in the docs
In my asp.net core 2.0 web app, I've got a custom ISecurityTokenValidator which validates tokens.
It depends on a repository to do a db lookup - the repository itself is setup as a scoped dependency:
services.AddScoped<IMyRepository>(MyRepository);
Now the funkiness comes about because of the way the ISecurityTokenValidator is setup.
It's added in ConfigureServices:
.AddJwtBearer(options =>
{
options.SecurityTokenValidators.Clear();
options.SecurityTokenValidators.Add(new MyTokenValidator(services.BuildServiceProvider()));
})
This is how it looks:
public class MyTokenValidator : ISecurityTokenValidator
{
private readonly IServiceProvider _serviceProvider;
public MyTokenValidator(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public bool CanReadToken(string securityToken) => true;
public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters,
out SecurityToken validatedToken)
{
var serviceScopeFactory = _serviceProvider.GetRequiredService<IServiceScopeFactory>();
using (var scope = serviceScopeFactory.CreateScope())
{
var myRepository = scope.ServiceProvider.GetService<IMyRepository>();
var principalFactory = scope.ServiceProvider.GetService<IUserClaimsPrincipalFactory<User>>();
// Use the repo....
}
}
}
Now, because the IsecurityTokenProvider is only instantiated once, it's effectively a singleton. When I use the service provider to ask for a IMyRepository I was finding that I was always received the same object - there is no new scope as far as it was concerned, because it's in a singleton class.
To get round that, you'll see in the code above Ive had to manually force a new scope every time the token validator is called. Is this really the only way to resolve this, it seems like I'm hacking around to make it work here...
Old question but the best way I have found to solve this problem is to use IPostConfigureOptions<JwtBearerOptions> to configure SecurityTokenValidators.
First register the JWT bearer and options
services.AddAuthentication(options =>
{
...
}).AddJwtBearer(AuthenticateScheme, options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
...
};
});
Then register a custom implementation of IPostConfigureOptions<JwtBearerOptions>
services.AddSingleton<IPostConfigureOptions<JwtBearerOptions>, CustomJwtBearerOptionsPostConfigureOptions>();
And register a custom implementation of ISecurityTokenValidator
services.AddSingleton<MyCustomSecurityTokenValidator>();
CustomJwtBearerOptionsPostConfigureOptions could look something like:
public class CustomJwtBearerOptionsPostConfigureOptions : IPostConfigureOptions<JwtBearerOptions>
{
private readonly MyCustomSecurityTokenValidator _tokenValidator; //example dependancy
public CustomJwtBearerOptionsPostConfigureOptions(MyCustomSecurityTokenValidator tokenValidator)
{
_tokenValidator = tokenValidator;
}
public void PostConfigure(string name, JwtBearerOptions options)
{
options.SecurityTokenValidators.Clear();
options.SecurityTokenValidators.Add(_tokenValidator);
}
}
Now options.SecurityTokenValidators is configured by CustomJwtBearerOptionsPostConfigureOptions which is instantiated by dependency injection and can pass on the relevant decencies.
I am working on a website where I need to authorize the user through a service. I have managed to get windows authentication working if I use the AuthorizeAttribute (User.Identities will be set). My plan is to create a custom middleware that sets the roles/claims for the user but context.User is not set in the middleware. User.Identities will also not be set in the controllers where I don't add the AuthorizeAttribute.
My goal is to write a middleware that gets the windows username and calls a service with the username to get the roles the user has access to and then set the roles or claims for the user.
public class RoleMiddleware
{
private readonly RequestDelegate _next;
public RoleMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (!rolesSet)
{
var result = _service.GetRoles(context.User.Identity.Name);
//set roles
//set claims
}
await _next.Invoke(context);
}
}
Would a middleware be the correct place to do this and what do I need to do to get access to the username in the same way as I do when I use the AuthorizeAttribute in a controller?
In my opinion that's not the right way to do it. ASP.NET Identity provide rich set of classes which you can override and extend to fit your requirements.
If you want to inject roles bases on some custom service then you should override RoleStore (and maybe RoleManager too) and inject there your custom roles.
It will be also worth to take a look here: Using Role Claims in ASP.NET Identity Core
I solved it by using requirements
public class CustomFunctionRequirement : IAuthorizationRequirement
{
public CustomFunctionRequirement(string function)
{
Function = function;
}
public string Function { get; }
}
The handler
public class CustomFunctionHandler : AuthorizationHandler<CustomFunctionRequirement>
{
private readonly Service _service;
public CustomFunctionHandler(Service service)
{
_service = service;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomFunctionRequirement requirement)
{
var functions = _service.GetFunctions(context.User.Identity.Name);
if (functions.Any(x => x == requirement.Function))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
Setup in ConfigureServices in Startup
services.AddMvc(
config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
services.AddAuthorization(
options =>
{
options.AddPolicy("User", policy => policy.Requirements.Add(new CustomRequirement("User")));
});
I can now in my controller specify the requirement by adding the authorize attribute [Authorize(Policy = "User")].
I am trying to customize the ASP.NET identity to use integer based keys instead of string. The code compiles but when I run the app, it throws an error for violation of type constraint. Here's how my identity classes look:
public class AppUser: IdentityUser<int, AppUserClaim, AppUserRole, AppUserLogin>
{
public DateTime FirstTrip { get; set; }
}
public class AppRole : IdentityRole<int, AppUserRole, AppRoleClaim>
{
}
public class AppUserClaim : IdentityUserClaim<int>
{
}
public class AppRoleClaim : IdentityRoleClaim<int>
{
}
public class AppUserLogin : IdentityUserLogin<int>
{
}
public class AppUserRole : IdentityUserRole<int>
{
}
public class AppUserToken : IdentityUserToken<int>
{
}
My AppDbContext class:
public class AppDbContext: IdentityDbContext<AppUser,AppRole, int, AppUserClaim, AppUserRole, AppUserLogin, AppRoleClaim, AppUserToken>
{
//other code
}
And here's how I am setting up identity in my startup.cs class:
services.AddIdentity<AppUser, AppRole>(config => //register ASPNET Identity
{
config.User.RequireUniqueEmail = true;
config.Password.RequiredLength = 8;
config.Cookies.ApplicationCookie.LoginPath = "/Auth/Login";
})
.AddEntityFrameworkStores<AppDbContext, int>();
When I run the app, I get following error:
This setup is in line with some suggestions like:
Why does this violate the type constraint?
ASP.NET identity configuration exception and that breaks dotnet.exe run process
What am I missing?
For anyone running into similar problems, here's what I had to do make it work:
1) Implement custom UserStore and RoleStore to account for changed TKey.
2) Register your custom UserStore and RoleStore in the ConfigureServices method in startup.cs. So Instead of the suggested,
services.AddIdentity<AppUser, IdentityRole>()
.AddEntityFrameworkStores<AppDbContext, int>();
use the following,
services.AddIdentity<AppUser, IdentityRole>()
.AddUserStore<AppUserStore<AppDbContext>>()
.AddRoleStore<AppRoleStore<AppRole, AppDbContext>>();
If you are looking for actual implementation of the custom User and Role store, you can check the following link out(at the very bottom look for PR1):
https://github.com/aspnet/Identity/issues/970
I am using OAuth bearer authentication, configured like this in Startup.cs:
OAuthBearerAuthenticationOptions oAuthBearerOptions =
new OAuthBearerAuthenticationOptions
{
AccessTokenProvider = new AccessTokenProvider(),
AuthenticationMode = AuthenticationMode.Active
};
app.UseOAuthBearerAuthentication(oAuthBearerOptions);
... where AccessTokenProvider is implemented as:
public class AccessTokenProvider : AuthenticationTokenProvider
{
public override async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
// Internal logic to get data needed for building identity...
// Create claims identity
ClaimsIdentity identity = new ClaimsIdentity(identityName);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, nameIdentifier));
// Add other claims
// Set claims identity
context.SetTicket(new AuthenticationTicket(identity, new AuthenticationProperties()));
}
}
If I set a breakpoint at the end of ReceiveAsync, I can verify that the identity is built correctly (has claims) and that SetTicket is reached.
But when I try to access the identity from a Web API controller:
public abstract class BaseStorageController : ApiController
{
protected IStorageService StorageService;
protected BaseStorageController(IStorageServiceFactory storageServiceFactory)
{
StorageService = storageServiceFactory.CreateStorageService(User.Identity as ClaimsIdentity);
}
}
... the list of claims on the identity is empty!
What can be causing this?
Side note: I don't know if this is related, but I am using Castle Windsor as an IOC container to inject dependencies into my controllers (in the above case, IStorageServiceFactory). The above seemed to work (claims were not empty) before I added that. However, I'm not using CW to manage anything related to authentication. Here is my CW installer for api controllers:
public class ApiControllerInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly().BasedOn<ApiController>().LifestylePerWebRequest());
}
}
I found the answer. It was not related to dependency injection/inversion of control. I'm not sure how I thought it was working prior to adding that.
The issue is similar to what is described here (but in my case the solution is different): User (IPrincipal) not avaliable on ApiController's constructor using Web Api 2.1 and Owin
Basically IPrincipal is not accessible from the constructor of the api controller, which is why there are no claims (the user is not yet authenticated). User.Identity is only accessible from the controller's actions, not the constructor. I changed my base controller implementation to the following to get around this issue:
public abstract class BaseStorageController : ApiController
{
private readonly IStorageServiceFactory _storageServiceFactory;
private IStorageService _storageService;
protected BaseStorageController(IStorageServiceFactory storageServiceFactory)
{
_storageServiceFactory = storageServiceFactory;
}
protected IStorageService StorageService
{
get
{
if (_storageService == null)
{
_storageService = _storageServiceFactory.CreateStorageService(User.Identity as ClaimsIdentity);
}
return _storageService;
}
}
}
Since StorageService is only accessed from controller actions, User.Identity is authenticated and has claims populated by the time that the StorageService getter gets called.
Hope this helps someone!
protected IStorageService StorageService
{
get
{
if (_storageService == null)
{
_storageService = _storageServiceFactory.CreateStorageService(User.Identity as ClaimsIdentity);
}
return _storageService;
}
}
this is not the best approach for implementing DI
It's much better to use constructor injection.
Check Constructor Injection in C#/Unity?
if you are not familliar with Unity, follow this link, very useful:
https://msdn.microsoft.com/en-us/library/dn223671(v=pandp.30).aspx
Regards