I am using MVC 5 with EntityFramework 6. In "IdentityModel" I am inheriting IdentityUser class using following code:
public class ApplicationUser : IdentityUser
{
[Display(Name="First Name")]
[StringLength(50, ErrorMessage = "Length exceed")]
public string FirstName { set; get; }
[Display(Name = "Last Name")]
[StringLength(50, ErrorMessage = "Length exceed")]
public string LastName { set; get; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
this.Configuration.LazyLoadingEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<IdentityUser>().HasKey(r => r.Id);
modelBuilder.Entity<IdentityUserLogin>().HasKey<string>(l => l.UserId);
modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id);
modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId });
}
I have assigned roles to "IdentityUser" using the following code:
UserManager.AddToRole(user.Id, "User");
I can see assigned roles in "IdentityUserRole" table, but when trying to access the roles using
User.IsInRole("User");
It returns always false.
After adding following code in my web.config I get "Could not find stored procedure 'dbo.aspnet_CheckSchemaVersion'." this error.
<roleManager enabled="true" defaultProvider="SqlRoleProvider">
<providers>
<clear/>
<add name="SqlRoleProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="DefaultConnection" />
</providers>
</roleManager>
I am not using code first migration.
User.IsInRole uses the 'old' version of role manager whereas your code is using the newer Identity framework. Instead use the new UserManager (you seem to already have the object):
if (UserManager.IsInRole(user.Id, "Admin"))
{
//...
}
This version work for me:
if (UserManager.IsInRole(User.Identity.GetUserId(), "Admin"))
{
Response.Write("IS IN ADMIN!");
}
If you want use Authorize attribule with role name like [Authorize(Roles = "Admin")] in your action, i recomend watch this topic:Authorize attribute not working with roles
Related
I am developing a task tracker web app and you could see the full code here: https://github.com/KimSergey94/TaskTracker
In short, admin, manager, client and employee are users of the app and admin is the boss of these roles. To illustrate, a manager can create a task received by a client and assign it to an employee. The task includes Statuses and Statuses includes Comments. The "roles" have user id as a foreign key to the User table that stores their email addresses and passwords. The Role table stores user id so that they have their roles right.
I need to develop basic functionality, make use some of AJAX, custom filters, stored procedures. There is something wrong with my authorization and roles logic. So, I would appreciate if you take a look and inform me about anything that you feel is not right.
Currently, when I am trying to launch the app and initialise the database, I get the following error:
System.Data.SqlClient.SqlException: 'Introducing FOREIGN KEY constraint 'FK_dbo.Employees_dbo.Users_UserId' on table 'Employees' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
User and Employee classes code:
public class User
{
public int UserId { get; set; }
[DataType(DataType.EmailAddress)]
[Required]
public string Email { get; set; }
public string Password { get; set; }
public int RoleId { get; set; }
}
public class Employee
{
public int EmployeeId { get; set; }
//[Required(ErrorMessage = "First Name is required")]
public string FirstName { get; set; }
//[Required(ErrorMessage = "Last Name is required")]
public string LastName { get; set; }
//[Required(ErrorMessage = "Country field is required")]
public string Country { get; set; }
public string Position { get; set; }
public int Salary { get; set; }
public int UserId { get; set; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
//public virtual ICollection<Task> Tasks { get; set; }
}
Globals.asax
protected void Application_Start()
{
System.Data.Entity.Database.SetInitializer<TaskTrackerContext>(new TaskTrackerDbInitializer());
var db = new TaskTrackerContext("TaskTrackerContext");
db.Database.Initialize(true);
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ModelValidatorProviders.Providers.Clear();
NinjectModule orderModule = new OrderModule();
NinjectModule serviceModule = new ServiceModule("TaskTrackerDb");
var kernel = new StandardKernel(orderModule, serviceModule);
DependencyResolver.SetResolver(new NinjectDependencyResolver(kernel));
}
web.config:
<connectionStrings>
<add name="TaskTrackerContext"
connectionString="Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename='|DataDirectory|\TaskTrackerContext.mdf';MultipleActiveResultSets=True; Integrated Security=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
<appSettings>
<add key="webpages:Version" value="3.0.0.0" />
<add key="webpages:Enabled" value="false" />
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
<!--
Initial Catalog=FormsAuth;
</appSettings>
By the way why do I need this Initial Catalog=FormsAuth;?
I noticed that with this parameter I could not instantiate database.
I want to get Include functionality for my models
In the EF Core, You should disable cascade delete with DeleteBehavior.Restrict or DeleteBehavior.SetNull, e.g. in the database context class, enter a new method
protected override void OnModelCreating(ModelBuilder modelBuilder){
modelBuilder.HasOne(x => x.Employee).WithMany().HasForeignKey(x =>
x.UserId).OnDelete(DeleteBehavior.Restrict)
}
If you want cascade behavior, you need to add a nullable integer to the UserId:
public int? UserId { get; set; }
Initial Catalog=FormsAuth; is from the System.Web.Security namespace and is used for form validations, you can read more about it on the Microsoft Docs: https://learn.microsoft.com/en-us/dotnet/api/system.web.security.formsauthentication?view=netframework-4.8
I recommend using asp.net identity, I can see a lot of what you are doing is re-inventing the wheel, whereas asp.net does the authentication and validation for you. You can read about it here: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-3.0&tabs=visual-studio
I had a table that had a circular relationship with others and I was getting the same error. Turns out it is about the foreign key which was not nullable. If the key is not nullable related object must be deleted and circular relations doesn't allow that. So use a nullable foreign key.
public int? UserId { get; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
I have a dbcontext class where i have initialized 4 dbsets. My connection string is
<connectionStrings>
<add name="somename" connectionString="Data Source=.; initial catalog=someDb; user ID=ab; Password:111111; MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" />
</connectionStrings>
My dbcotext class is
public AstroEntities(): base("somename")
{
Database.SetInitializer<AstroEntities>(new CreateDatabaseIfNotExists<AstroEntities>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Contact>().ToTable("Contacts");
modelBuilder.Entity<Appointment>().ToTable("Appointments");
modelBuilder.Entity<Consultation>().ToTable("Consultations");
modelBuilder.Entity<HomePageMessage>().ToTable("HomePageMessages");
base.OnModelCreating(modelBuilder);
}
public DbSet<Contact> Contacts { get; set; }
public DbSet<Appointment> Appointments { get; set; }
public DbSet<Consultation> Consultations { get; set; }
public DbSet<HomePageMessage> Homepagemessages { get; set; }
}
When i enable automatic migrations iam getting error as follows
"Keyword not supported: 'password:111111; multipleactiveresultsets'."
Can someone say whats the problem?
Your Connection String format is wrong, it should be like this
connectionString="Data Source=.; initial catalog=someDb; user ID=ab; Password=111111; MultipleActiveResultSets=True;"
I am getting below error from objContext.warrantys property of entity framework.
Error Message:A null was returned after calling the 'get_ProviderFactory' method on a store provider instance of type 'System.Data.OleDb.OleDbConnection'. The store provider might not be functioning correctly.
Local 'objContext.warrantys.Local' threw an exception of type 'System.InvalidOperationException' System.Collections.ObjectModel.ObservableCollection<DSGWarrantyServiceRep.Core.warranty> {System.InvalidOperationException}
In Web.config,
public class warranty
{
public int WarrantyId
{
get;
set;
}
[Required(ErrorMessage =" Please provide Warrranty Name")]
[MinLength(4)]
public string WarrantyName
{
get;
set;
}
[Required(ErrorMessage = " Please provide Warrranty Period")]
public string WarrantyPeriod
{
get;
set;
}
[Required(ErrorMessage = " Please provide UPC Nbr")]
public string UpcNbr
{
get;
set;
}
public warranty()
{
}
}
Now, I am going to add dbcontext class.
public class WarrantyContext:DbContext
{
public WarrantyContext() : base("name=warrantyConnectionString")
{
}
public DbSet<warranty> warrantys { get; set; }
}
Now From My warranty service class implemented here
public class WarrantyService : IWarrantyService
{
WarrantyContext objContext = new WarrantyContext();
public void AddWarranty(warranty ws)
{
objContext.warrantys.Add(ws);
objContext.SaveChanges();
}
public IEnumerable<warranty> GetAllWarranty()
{
**return objContext.warrantys;**
}
}
It looks as if you need to check your connection string in the web.config (web app) or app.config (other). Feel free to post it here too. You seem to be using the OLEDB, and you should be using native SqlClient instead.
You should have something that looks like this (local db in this case, but could be SQL for you), for example:
<connectionStrings>
<add name="warrantyConnectionString" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-TestApp1-20170112094217.mdf;Initial Catalog=aspnet-TestApp1-20170112094217;Integrated Security=True" providerName="System.Data.SqlClient" />
</connectionStrings>
I'm migrating from Identity 1.0.0 to Identity 2.0.1 following this article
and the migrations code generated is nothing about the new IdentityUser. It doesn't add the new columns.
So I made a new project and tried again but the migrations codes is empty.
To fix that problem, I did the edits directly in SQL Server and imported my database again in my solution.
Now my AspNetUser is exactly the same as my IdentityUser as you can see
IdentityUser
public virtual int AccessFailedCount { get; set; }
public virtual ICollection<TClaim> Claims { get; }
public virtual string Email { get; set; }
public virtual bool EmailConfirmed { get; set; }
public virtual TKey Id { get; set; }
public virtual bool LockoutEnabled { get; set; }
public virtual DateTime? LockoutEndDateUtc { get; set; }
public virtual ICollection<TLogin> Logins { get; }
public virtual string PasswordHash { get; set; }
public virtual string PhoneNumber { get; set; }
public virtual bool PhoneNumberConfirmed { get; set; }
public virtual ICollection<TRole> Roles { get; }
public virtual string SecurityStamp { get; set; }
public virtual bool TwoFactorEnabled { get; set; }
public virtual string UserName { get; set; }
IdentityUser.cs
public class ApplicationUser : IdentityUser
{
public bool Has_accepted_policy { get; set; }
public int user_type_id { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
}
AspNetUser
public string Id { get; set; }
[Required]
[StringLength(256)]
public string UserName { get; set; }
public string PasswordHash { get; set; }
public string SecurityStamp { get; set; }
[StringLength(256)]
public string Email { get; set; }
public bool EmailConfirmed { get; set; }
public bool Is_Active { get; set; }
[Required]
[StringLength(128)]
public string Discriminator { get; set; }
public int? user_type_id { get; set; }
public bool Has_accepted_policy { get; set; }
public string PhoneNumber { get; set; }
public bool PhoneNumberConfirmed { get; set; }
public bool TwoFactorEnabled { get; set; }
public DateTime? LockoutEndDateUtc { get; set; }
public bool LockoutEnabled { get; set; }
public int AccessFailedCount { get; set; }
... other virtual properties
and when I try to register a user I have the following exception
The entity type ApplicationUser is not part of the model for the current context
at this line
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
My startup.Auth.cs
UserManagerFactory = () => new UserManager<ApplicationUser>(new UserStore<ApplicationUser>());
And in my AccountController I declare my UserManager like this
public AccountController()
: this(Startup.UserManagerFactory(), Startup.OAuthOptions.AccessTokenFormat)
{
}
public AccountController(UserManager<ApplicationUser> userManager,
ISecureDataFormat<AuthenticationTicket> accessTokenFormat)
{
UserManager = userManager;
AccessTokenFormat = accessTokenFormat;
}
public UserManager<ApplicationUser> UserManager { get; private set; }
I haven't changed anything except the new properties in the AspNetUser class and it used to work well before the migration.
There's a similar issue on CodePlex marked as fixed but they don't give the solution
Does anyone know how to fix this?
EDIT
To be sure I didn't do any mistakes when I edited my SQL database. I created another project and generated an Identity database and I changed the connection string for that database and I still have the same error.
SOLUTION
When I have edited my database I haven't noticed that in Identity 2.0.0 they changed the User_Id for UserId in AspUserClaims table. After doing that I had the same error but then I did what tschmit007 said about adding the ApplicationDbContext to the UserStore constructor and now it works.
UserManagerFactory = () => new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
I was having this same problem. I’m doing database first development with an EDMX file. If you are using the connection string generated when adding the EDMX file in :base(“EDMXConnString”) you will most likely have this problem.
I fixed this by creating a standard connection string that pointed to the database where the ASP.NET Identity tables are.
<add name="MyConnString" connectionString="Data Source=server; Initial Catalog=db_name; User ID=user_id; Password=password; Connect Timeout=60;" providerName="System.Data.SqlClient" />
And then used that connection string in :base, and it worked!
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("MyConnString")
{
}
}
for me it seems to miss a context instanciation:
UserManagerFactory = () => new UserManager<ApplicationUser>(new UserStore<ApplicationUser>());
should be
UserManagerFactory = () => new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
My problem was I tried to use generated ADO.NET connection string for both generated and authentication context ApplicationDbContext. I fixed it by using a separate connection string for authentication. Also pay attention to the provider - for authentication context it has to be System.Data.SqlClient:
<add name="DefaultConnection" connectionString="Server=qadb.myserver.com;Database=mydb;User Id=myuser;Password=mypass;" providerName="System.Data.SqlClient" />
If you are using code first, check your connection string to ensure providerName is 'SqlClient' as in providerName="System.Data.SqlClient
If you are using database first, check your connection string to ensure providerName is 'EntityClient' as in providerName="System.Data.EntityClient
Same problem to me, it solved by this code:
public ApplicationDbContext() : base("DefaultConnection", throwIfV1Schema: false)
{
Database.Connection.ConnectionString = #"data source=...;initial catalog=...;user id=...;password=...;multipleactiveresultsets=True;application name=EntityFramework";
}
I also received this error message, but the cause and solution were different. In my case, I had introduced a new Id property of type Guid in my ApplicationUser class. Perfectly valid C# syntax, but it apparently created massive confusion for the Identity or EntityFramework core that relies on reflection to find stuff.
Removing the new Id property in my ApplicationUser class resolved this error.
I ran into this issue and it was an object name conflict. The IdentityConfig.cs was using ApplicationUser, but it was using the auto-generated IdentityModels.ApplicationUser instead of my own context's DataAccess.ApplicationUser. Made perfect sense once I found it. So, I deleted the auto-generated IdentityModels.cs from the base WebAPI template - not using that anymore anyway - then I added the using statement in IdentityConfig.cs to my own DataAccess namespace and voila, proper mapping. If you forget the template built a lot of this for you, you'll run into the issue:
public class ApplicationUserManager : UserManager<ApplicationUser> // the name conflict
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
}
My issue was that I had created a new DbContext, but it wasn't inheriting from IdentityDbContext.
An easy fix...
public partial class GoldfishDbContext : IdentityDbContext<ApplicationUser>
{
....
}
I am sure not why this happens, my solution works perfectly fine, tested everything before I sleep. After 12 hours, I checked it again and run and this was exactly the same error. I tried almost all solutions here in SO but none of them works.
I am implementing a database approach here. Then suddenly there was this
DefaultConnection
on my web.config that Visual Studio generated when I first created the solution. So I've used it instead of the connection string that was generated by my EDMX file and suddenly it works!
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
This was my connection string that works:
<add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-System.WEB-20180718085411.mdf;Initial Catalog=aspnet-System.WEB-20180718085411;Integrated Security=True" providerName="System.Data.SqlClient" />
Originally I am using this that was generated by my EDMX file but suddenly the website doesn't work although it works before. I didn't change anything and all code was in TFS, so I am 100% sure it works and I did a full restore and get the latest version:
<add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\aspnet-System.WEB-20180718085411.mdf;Initial Catalog=aspnet-System.WEB-20180718085411;Integrated Security=True" providerName="System.Data.SqlClient" />
This happened to me because I was trying to wire up ApplicationUserManager and some other related dependencies using my dependency injection container. In some cases, the container resolved ApplicationDbContext in other cases the built-in injector in Owin would resolve it.
The easiest way to make sure this doesn't happen is to not try to wire up any of the Auth stuff using your DI container of choice unless you really know what youre doing with DI...otherwise just let Owin resolve it using the built in injector.
In other words, remove anything like:
builder.RegisterType<ApplicationUserManager>().InstancePerRequest();
And just let Owin resolve it the way it was built in:
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
I want to build a Registration system where, while adding an user u can select the type of role you can give to him/her. And depending upon the role he/she it would be decided whether certain action in controller can be accessed or not.
For example, let's say there are two roles, admin and developer.
And having something like mentioned below would only allow user with roles as admin to acces following action.
[Authorize(Roles = "admin"]
public ActionResult CreateUser()
{
return View();
}
As far as I know I have to implement my custom RoleProvider or IPrincipal?
I tried to find some example on that but didn't quite get what i'm exactly looking for.
Here is how my RegisterModel currently looks like
public class RegisterModel
{
[Key]
public Guid Id;
[Required]
[Display(Name="First Name")]
public string FirstName {get; set;}
[Required]
[Display(Name="Last Name")]
public string LastName {get; set;}
[Required]
[Display(Name="Email Id")]
[DataType(DataType.EmailAddress)]
public string EmailId {get; set;}
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[Display(Name = "Password")]
[DataType(DataType.Password)]
public string Password { get; set; }
[Required]
[Display(Name = "Confirm Password")]
[DataType(DataType.Password)]
public string ConfirmPassword { get; set; }
[Required]
[Display(Name = "Role")]
public UserRole Role { get; set; }
}
public class UserRole
{
[Key]
public int RoleId { get; set; }
public string RoleName { get; set; }
}
Thing is I want the role to be decided when adding a user and use the Custom Authorize attribute. Any article or blog that anyone knows that can solve my issue? Or any suggestions, how to do it?
Recently i implemented Role authorization without using Memberhip provider. Thought this might help you.I have a database table with UserName, Password and a Role and i needed to check the role against the database.
Below is my custom RoleFilter class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcApplicationrazor.Models.ActionFilters
{
public class RoleFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (GetCurrentUserRole() != "Admin")// Check the Role Against the database Value
{
filterContext.Result = new RedirectResult("~/Redirect/NoPermission");
return;
}
}
}
}
Controller:
[RoleFilter]//Check the Role, if not allowed redirect to NoPermission view
public ActionResult Index()
{
return View();
}
MVC 4 uses some helper classes from WebMatrix to implement Security and Memberhip. You can read a very nice tutorial here:
http://www.asp.net/web-pages/tutorials/security/16-adding-security-and-membership
If you don't have any special requirements, it's usually not worth it to come up with your own implementation of a Role Provider.
Good Luck!
EDIT: A QUICK TUTORIAL
The following is based on a Model class called "UserProfile" with a corresponding table named the same. This table has a column called "UserId" for the id and one called "UserName" for login. Of course it can have all the info you need, but these are the only ones needed by the WebSecurity to initialize the DB.
Step 1: the web.config. Put this in the system.web section. This instructs ASP.NET to use the two Simple providers for Role and Membership:
<roleManager enabled="true" defaultProvider="simple">
<providers>
<clear/>
<add name="simple" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData"/>
</providers>
</roleManager>
<membership defaultProvider="simple">
<providers>
<clear/>
<add name="simple" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData"/>
</providers>
</membership>
Step 2: Application_Start. Add the initialization for your DB for roles and membership tables:
protected void Application_Start()
{
try
{
// Initializes the DB, using the "DefaultConnection" connection string from the web.config,
// the "UserProfile" table, the "UserId" as the column for the ID,
// the "UserName" as the column for usernames and will create the tables if they don't exists.
// Check the docs for this. Basically the table you specify
// is a table that already exists and where you already save your user information.
// WebSecurity will simply link this to its own security info.
if (!WebSecurity.Initialized)
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
}
catch (Exception ex)
{
throw new InvalidOperationException("Cannot init ASP.NET Simple Membership database", ex);
}
}
When the InitializeDatabaseConnection fires for the first time, it will create 4 tables:
webpages_Membership
webpages_OAuthMembership
webpages_Roles
webpages_UsersInRoles
Step 3: You can now use the Authorize attribute:
[Authorize(Roles="Admin")]
Also, you will now have a lot of methods to create and login your users:
WebSecurity.CreateUserAndAccount(model.UserName, model.Password); // to create users. You can also pass extra properties as anonymous objects
WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe); // for logins
WebSecurity.Logout();
WebSecurity.ChangePassword(User.Identity.Name, model.OldPassword, model.NewPassword);
// and so on...
I find this approach to be a lot more flexible (and quicker) than rolling your own implementation.