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.
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 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
I’m exploring ASP.NET MVC 4 these days. I will be pleased if someone can help by answering my question .
I am building an academic project "Project management and support system" . I have designed my own database , I have my own tables for the users in my database (two kinds of users : Employee who will execute tasks , and a client who assign/hire for tasks ) ,I was about creating a new membership provider but I realized that "It's a waste of time - reinventing the wheel".
Now , I am building a membership model on ASP.NET MVC4 using SimpleMembership (It's the future of membership services for MVC applications ).It provides a more concise membership provider to the ASP.NET framework, and supports OAuth additionally.
1- I created an out-of-the-box ASP.NET MVC 4 internet application to customize the Login, Signup and User management logic to maintain the user-profile table.
I added three roles : Admin , Employee , Client
Going through this blogpost , I am able to customize the registration http://blog.longle.net/2012/09/25/seeding-users-and-roles-with-mvc4-simplemembershipprovider-simpleroleprovider-ef5-codefirst-and-custom-user-properties/
2- Now , I am working synchronize this table with the tables of the users that I have in my own database . Taking in consideration that I have added another field of "Account Type" Asking the user during the registration to create a specific profile .
Any help greatly appreciated.
Cheers
with SimpleMembership there are 2 ways for storing and using that information for authentication.
you may use default (UserProfiles) table, that is in database pointed to by "DefaultConnection" string.
you may use YOUR database and a table therein to be used as replacement of default UserProfiles table.
option 1 is explained very well elsewhere. for option 2 follow steps given below:
suppose your database context is mDbContext and table that you want to use for replacement of UserProfiles is Employees.
your Employee model looks like this
namespace m.Models
{
public class Employee
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Mobile { get; set; }
public Designation Designation { get; set; }
.........
your DbContext looks like this
namespace m.Models
{
public class mDBContext : DbContext
{
DbSet<Employee> Employees { get; set; }
......
you need to tell WebSecurity to use your database.
WebSecurity.InitializeDatabaseConnection("mDBContext",
"Employees", "ID", "UserName", autoCreateTables: true);
add additional fields in RegisterModel class in AccountModels
public class RegisterModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Mobile { get; set; }
public Designation Designation { get; set; }
}
In AccountController Register method for HttpPost replace
WebSecurity.CreateUserAndAccount(model.UserName, model.
with
WebSecurity.CreateUserAndAccount(model.UserName, model.Password,
new { FirstName = model.FirstName, LastName = model.LastName,
Mobile = model.Mobile});
rebuild and update database if pending any changes (or add migrations).
Follow the answer on similar question to the link below. If you have any more questions let me know.
Similar Question with answer
Update
After reading your first comment, sounds like you need to first understand what MVC is all about before you touch on SimpleMembership. try the following links.
wikipedia
w3schools
MSDN
http://www.asp.net/mvc
So I have implemented my own custom membership provider, currently only overwriting ValidateUser(). I also have signalr working, currently just a simple method that calls send message and then sends it out to all listeners. I would like to add some validation checking so that this command cannot be run on its own. So far I have found you can do this using: Context.User.Identity.IsAuthenticated
[HubName("messageController")]
public class MessageController : Hub
{
public void SendMessage(string message)
{
if (Context.User.Identity.IsAuthenticated) // this line not working
{
Clients.addMessage(message);
}
else
{
Clients.addMessage("not authenticated");
}
}
}
My problem though is because I am currently using a custom membership provider value is false. Is there something else I should be using here instead?
Currently when I Login I execute:
[AllowAnonymous]
[HttpPost]
public ActionResult Login(LoginModel model, string returnUrl)
{
if(Membership.ValidateUser(model.UserName, model.Password))
{
// Need something here to actually set the logged in user
}
return RedirectToAction("Index", "");
}
What am I missing here? Do I need to store my own code to handle the session, I tried using FormsAuthentication.SetAuthCookie(model.UserName, true); which worked but I'm pretty sure its wrong. When I tried to introduce Roles by changing it to Context.User.IsInRole("Admin") it returned false. even though I used the below User model (which when debugging never gets to this method):
public class User : IPrincipal
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
public long UserID;
public string Name;
public string ConnectionId;
public virtual IIdentity Identity
{
get;
set;
}
public virtual bool IsInRole(string role)
{
return true;
}
}
I'm pretty confident I am missing something with this, any ideas?
Using FormsAuthentication.SetAuthCookie() after you've validated the user is perfectly OK, the default AccountController created by the MVC3 template does exactly the same.
The reason why your call to Context.User.IsInRole() doesn't work is because that won't invoke your custom User class (the framework doesn't know about it), instead it will try to get the roles via a RoleProvider. You need to build a custom provider and hook it up in the Web.config like you did with the membership provider.
I am developing a ASP.NET MVC 3 application, i am using entity framework code first in order to create the classes of my app, and i also have a repository in order to perform the operations on it, keeping clean the DBContext and the DBEntities definitions.
My doubt is about the render of the views and the way where a edit model is saved.
If I have this entity that represent a user stored in my DB:
//Entity:
public class User
{
[Key]
public int IdUser { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
And i want to show a View with the FirstName, LastName, Email and NewPassword, ConfirmPasword and CurrentPassword, in order to let the user change his data, typing the CurrentPassword to confirm the changes, so my doubt is, fieds like ConfirmPasword and CurrentPassword aren´t in my entity so i need to create a new model for this View and the copy the information that i want from my new Model to my database entity in order to save it? Like:
public class UpdateUserModel
{
[Required]
[Display(Name = "Name")]
public string FirstName{ get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName{ get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Not valid email")]
public string Email { get; set; }
[DataType(DataType.Password)]
[Display(Name = "New password")]
public string NewPasword{ get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm the New Pasword")]
[Compare("NewPasword", ErrorMessage = "Password doesn´t mach.")]
public string ConfirmPasword{ get; set; }
[Required(ErrorMessage = "Need to specify the current password to save changes")]
[DataType(DataType.Password)]
[Display(Name = "Current Password")]
public string CurrentPassword { get; set; }
}
and in the controller i made:
public ActionResult UpdateUser(UpdateUserModel model)
{
User u = (User)Membership.GetUser();
u.FirstName = model.FirstName;
u.LastName = model.LastName;
u.Email = model.Email;
if (!String.IsNullOrEmpty(model.NewPassword))
{
u.Password = FormsAuthentication.HashPasswordForStoringInConfigFile(model.NewPassword.Trim(), "md5");
}
repository.UpdateUser(u);
return View();
}
There are any way of doing this having a controller like:
public ActionResult UpdateUser(User u)
{
repository.UpdateUser(u);
return View();
}
Because if i have that, how i can add the field like, ConfirmPassword or CurrentPassword in order to made the validation for this specific View.
If I were you, I wouldn't use domain model in my presentation layer. I would create a view model (another class) which will be very similar to my domain model. I would then use auto-mapping tool to map from my domain model to the view model.
This is a very common scenario, so if you Google for "view and domain" models you should find everything you need.
public class User {
[Key]
public int IdUser { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
public class UpdateUserViewModel {
// Original fields
public string Password { get; set; }
public string PasswordConfirmation { get; set;
}
You could then configure auto-mapper to remove your boiler plate code:
public ActionResult ShowUser()
{
var domainModel = new User(); // I'm assuming that properties are set somewhere
var viewModel = new UserViewModel();
Autommaper.Map(domainModel, viewModel);
return View(viewModel);
}
This is very rough, but hopefully you get an idea.
Update 1: **
As i understood is better to create a new model for each view and then map it into the entity
It's not just better, it provides better separation of concerns, makes your code easily testable. Just by looking at the name of the class, I can see its purpose UpdateUserViewModel, RegisterUserViewModel etc).
Original fields, in this class is supposed to be the Metadata with the validation and that stuff isn't?
By original fields I mean:
public class UserViewModel{
public string UserName { get; set; }
public string FirstName { get; set; }
}
These fields are already in your User class, so I saved my time by not typing them in again.
This will be change my model from MVC to MVVM or not beacuse i still have a controller?
I believe what I've suggested is still an MVC pattern, rather than MVVM.
About the Automaper, are you using github.com/AutoMapper/AutoMapper?
Automapper is something that I have used. There are few tools out there and they do pretty much the same thing. Try out few and find one that suits your requirements the most.
Good luck.
Usually I use areas for different parts of my project, as an aside of where to put this extra code.
Pretty much you are going to add to your model folder a viewmodel.cs class. Inside this class will hold your definitions for how the data will be modelled in the view. These viewmodels will reflect the parts of the entity you wish the user to interact with. The interaction will be done in the controllers via [HttpGet] where you pass in the view model to be interacted with, and [HttpPost] where you send the model back and then map it to an entity.
ViewModels.cs:
public class UserViewModel
{
public string UserName { get; set; }
}
SomeController:
public ActionResult getView()
{
var uvm = new UserViewModel();
return View(uvm);
}
View getView.cshtml:
#model project.namespace.UserViewModel
#using (Html.BeginForm())
{
#Html.EditorFor(m => m.UserName)
<input type="submit" value="New User Name" />
}
Back in controller:
[HttpPost]
public ActionResult getView(UserViewModel model)
{
var entity = new ActualEntity();
entity.username = model.UserName;
//more mapping
//commit changes somewhere
return RedirectToAction("getView");
}