I have recently started to explore ASP.NET MVC and came across a scenario. I want to have expert's opinion over its implementation approach in MVC
Scenario:
We have two users classes:
Normal users
Power users
Normal users can only enter transactional data which pertains to their work area. For example, if there exists two work areas (W1 & W2) and user 1 is mapped to work area W1 then he can only enter transactional data of W1. Whereas, if user 2 is mapped to both work areas (i.e. W1 & W2) then the user can enter transactional data of any of the areas.
Power users , as name suggest, are super user. They can enter transactional data of any work area and can change Master Data of the application as well.
I want to use Windows Authentication for user authentication and for authorization I want to have a table in DB where domain user ids(AD) of users are mapped to relevant work areas along with their user type (normal/power).
My question is how this could be done in ASP.NET MVC5. Any lead towards its solution or pointer to any relevant article/tutorial will be highly appreciated.
Further to this, if I want to generate dynamic menu (each menu item is mapped to corresponding Action) at the time of authentication based on the authenticated user type then how it can be done.
Thanks for your time.
Based on my understanding of your question, you want to authenticate users with Active Directory, then authorize with local authorization mechanism.
If so, you could use OWIN cookie authentication middleware in ASP.NET MVC 5.
It has few moving pieces, so I created a sample application at GitHub. The followings are the database diagram and two main classes.
OwinAuthenticationService
private readonly HttpContextBase _context;
private const string AuthenticationType = "ApplicationCookie";
public OwinAuthenticationService(HttpContextBase context)
{
_context = context;
}
public void SignIn(User user)
{
IList<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.Sid, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.GivenName, user.FirstName),
new Claim(ClaimTypes.Surname, user.LastName),
};
foreach (string roleName in roleNames)
{
claims.Add(new Claim(ClaimTypes.Role, roleName));
}
ClaimsIdentity identity = new ClaimsIdentity(claims, AuthenticationType);
IOwinContext context = _context.Request.GetOwinContext();
IAuthenticationManager authenticationManager = context.Authentication;
authenticationManager.SignIn(identity);
}
public void SignOut()
{
IOwinContext context = _context.Request.GetOwinContext();
IAuthenticationManager authenticationManager = context.Authentication;
authenticationManager.SignOut(AuthenticationType);
}
Startup.cs
You also need to configure Startup for all those to happen.
[assembly: OwinStartup(typeof(YourApplication.Startup))]
namespace YourApplication
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "ApplicationCookie",
LoginPath = new PathString("/Account/Login")
});
}
}
}
Usage
Then you can start using [Authorize] attribute in Controller and Action methods.
[Authorize(Roles = "Power Users")]
public class UsersController : Controller
{
// ...
}
Related
We have a ServiceStack host, in which we have modularised the services. In addition we have a custom authentication solution based on the Basic Authentication. But what we would like to do is have different authentication methods for different services, maybe based on routes? Is this possible?
Secondly, is it possible to assign a common route prefix based on the service? As I said we have modularised our services, and in the AppHost definition we enter the assemblies of the different services, but is it possible to change the route prefix, i.e. Service1 to localhost/api1/servicemethods, Service2 to localhost/api2/servicemethods etc.?
You can limit that a Service should only authenticate with a specific provider by specifying the provider name in the [Authenticate] attribute, e.g:
[Authenticate(AuthenticateService.ApiKeyProvider)]
public class ApiKeyAuthServices : Service
{
public object Any(ApiKeyOnly request) => ...;
}
[Authenticate(AuthenticateService.JwtProvider)]
public class JwtAuthServices : Service
{
public object Any(JwtOnly request) => ...;
}
Otherwise inside your Service you can inspect how the request was authenticated by looking at base.SessionAs<AuthUserSession>().AuthProvider.
For defining dynamic routes have a look at:
Auto Route Generation Strategies
Dynamically adding Route Attributes
Customizing Defined Routes
Although ServiceStack isn't designed to define different sets of Apps within the same AppHost so if that's what you're trying to do I'd recommend instead having different AppHosts and using the Service Gateway for any Service-to-Service communication.
Many thanks for your reply. I must be doing something fundamentally wrong, even though I have registered two custom authproviders, both based on the BasicAuthProvider, using AuthenticateService.GetAuthProviders() returns an empty array.
This is the code I use to register the AuthProviders, and they both allow me to login, so I know they are working.
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new RMCredentialsAuthProvider(),
new RMKOTAuthProvider()
}));
The code from one of the custom providers is
public class RMKOTAuthProvider : BasicAuthProvider
{
#region Public Constructors
public RMKOTAuthProvider() : base()
{
}
#endregion Public Constructors
#region Public Methods
public override Task<IHttpResult> OnAuthenticatedAsync(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo, CancellationToken token = default)
{
session.FirstName = session.UserAuthName;
session.Roles = new List<string>
{
"KOT"
};
authService.SaveSessionAsync(session, SessionExpiry);
return base.OnAuthenticatedAsync(authService, session, tokens, authInfo, token);
}
public override Task<bool> TryAuthenticateAsync(IServiceBase authService, string userName, string password, CancellationToken token = default)
{
try
{
if (userName.IsNullOrEmpty() || password.IsNullOrEmpty())
return Task.FromResult(false);
var result = VerifyUser(username, password);
return Task.FromResult(result);
}
catch (InvalidCastException)
{
return Task.FromResult(false);
}
}
#endregion Public Methods
}
Can you please explain what step I am missing such that GetAuthProviders() can list the providers, and I can use the metadata you described earlier.
Many thanks in advance for your help with this.
I am wondering how to create and assign roles in Razor Pages 2.1. application.
I have found how to make them for MVC application (How to create roles in asp.net core and assign them to users and http://hishambinateya.com/role-based-authorization-in-razor-pages), however it does not work for razor pages as I have no IServicesProvider instance.
What I want is just to create admin role and assign it to seeded administrator account. Something similar has been done in this tutorial https://learn.microsoft.com/en-us/aspnet/core/security/authorization/secure-data?view=aspnetcore-2.1, but it seems be sutied for MVC and does not work properly after I applied it to my application. Please help me to understand how to create and seed roles in Razor Pages.
Will be very greatfull for help!
I handle the task next way. First, I used code proposed by Paul Madson in How to create roles in asp.net core and assign them to users. Abovementioned method I have inserted into Startup.cs. It creates administrator role and assigned it to seeded user.
private void CreateRoles(IServiceProvider serviceProvider)
{
var roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
Task<IdentityResult> roleResult;
string email = "someone#somewhere.com";
//Check that there is an Administrator role and create if not
Task<bool> hasAdminRole = roleManager.RoleExistsAsync("Administrator");
hasAdminRole.Wait();
if (!hasAdminRole.Result)
{
roleResult = roleManager.CreateAsync(new IdentityRole("Administrator"));
roleResult.Wait();
}
//Check if the admin user exists and create it if not
//Add to the Administrator role
Task<ApplicationUser> testUser = userManager.FindByEmailAsync(email);
testUser.Wait();
if (testUser.Result == null)
{
ApplicationUser administrator = new ApplicationUser
{
Email = email,
UserName = email,
Name = email
};
Task<IdentityResult> newUser = userManager.CreateAsync(administrator, "_AStrongP#ssword!123");
newUser.Wait();
if (newUser.Result.Succeeded)
{
Task<IdentityResult> newUserRole = userManager.AddToRoleAsync(administrator, "Administrator");
newUserRole.Wait();
}
}
}
Then, in the same file in Configure method I add argument (IServiceProvider serviceProvider), so you should have something like Configure(..., IServiceProvider serviceProvider). In the end of Configure method I add
CreateRoles(serviceProvider).
To make this code work create ApplicationUser class somwhere, for example in Data folder:
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sobopedia.Data
{
public class ApplicationUser: IdentityUser
{
public string Name { get; set; }
}
}
Finally, inside ConfigureServices method substitute
services.AddIdentity<ApplicationUser>()
.AddEntityFrameworkStores<SobopediaContext>()
.AddDefaultTokenProviders();
with
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<SobopediaContext>()
.AddDefaultTokenProviders();
As a result, after programm starts in table AspNetRoles you will get a new role, while in table AspNetUsers you will have a new user acuiering administrator role.
Unfortunatelly, after you add the following code
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<SobopediaContext>()
.AddDefaultTokenProviders();
pages Login and Registration stop working. In order to handle this problem you may follow next steps:
Scaffold Identity following (https://learn.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-2.1&tabs=visual-studio).
Then substitute IdentityUser for ApplicationUser in entire solution. Preserv only IdentityUser inheritance in ApplicationUser class.
Remove from Areas/identity/Pages/Account/Register.cs all things related to EmailSernder if you have no its implementation.
In order to check correctness of the roles system you may do as follows. In the end of ConfigureServices method in Startup.cs add this code:
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdministratorRole", policy => policy.RequireRole("Administrator"));
});
services.AddMvc().AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/Contact","RequireAdministratorRole");
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
If it does not worki then just add [Authorize(Roles = "Administrator")] to Contact Page model, so it will look something like this:
namespace Sobopedia.Pages
{
[Authorize(Roles = "Administrator")]
public class ContactModel : PageModel
{
public string Message { get; set; }
public void OnGet()
{
Message = "Your contact page.";
}
}
}
Now, in order to open Contact page you should be logged in with login someone#somewhere.com and password _AStrongP#ssword!123.
Unsure how IMobileServiceSyncTable security works - say I have a table, and it stores data for multiple users.
Following this Azure App Services tutorial, it looks like I can query - from a mobile app - for pretty much any record, for any user, that I want.
Client-side (e.g., Xamarin):
await todoTable.PullAsync("todoItems" + userid,
syncTable.Where(u => u.UserId = userid));
Is there a way (server-side) to automatically scope records to the current authenticated user? Or is that done for you automatically if you decorate your table controllers with the [Authorize] attribute?
Server-side:
[Authorize]
public class TodoItemController : TableController<TodoItem>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MyAppContext context = new MyAppContext();
DomainManager = new EntityDomainManager<TodoItem>(context, Request);
}
// GET tables/TodoItem
public IQueryable<TodoItem> GetAllTodoItems()
{
return Query();
}
// GET tables/TodoItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<TodoItem> GetTodoItem(string id)
{
return Lookup(id);
}
}
Check out this blog post (assuming ASP.NET): http://shellmonger.com/2016/05/09/30-days-of-zumo-v2-azure-mobile-apps-day-18-asp-net-authentication/ - it adjusts the table controller to do exactly what you want.
In a public facing web app multi tenant app, I'm using ASP Identity 2.x.
I let 3rd party's e.g. non-profits/comps self-register, create and populate their own roles and users. The code below is fine, if its an intranet scenarios where everyone belongs to the same company, it does not for work multiple non-profits
The non-profits registrants (understandably so) are naming roles with the same names, i.e. Managers and Employees etc. which are common/same across the database.
How can I extend ASP Identity to separate the roles per organization in a multi-tenant fashion, can you help me understand the design and how extend this, do I need a sub-role? i.e.
what do I do to ensure roles are scoped per organization at the database, so that different org's can have the same role names?
and, what do I do at the middle tier, i.e. usermanager, role manager objects level (middle tier)
//Roles/Create
[HttpPost]
public ActionResult Create(FormCollection form)
{
try
{
context.Roles.Add(new Microsoft.AspNet.Identity.EntityFramework.IdentityRole()
{ \\ Question - can I add another level here like company??
Name = form["RoleName"]
});
context.SaveChanges();
ViewBag.ResultMessage = "Role created successfully";
return RedirectToAction("RoleCreated");
}
catch
{
return View();
}
}`
Question- When adding a role, how do I separate the role to know which Role is from Which company when I add to the user?
public ActionResult RoleAddToUser(string UserName, string RoleName)
{
ApplicationUser user = context.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
var account = new AccountController();
account.UserManager.AddToRole(user.Id, RoleName);
ViewBag.ResultMessage = "Role created successfully !";
// prepopulat roles for the view dropdown
var list = context.Roles.OrderBy(r => r.Name).ToList().Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
ViewBag.Roles = list;
return View("ManageUserRoles");
}
how do I get the list of Roles and Users for that non-profit?
public ActionResult ManageUserRoles()
{
var list = context.Roles.OrderBy(r => r.Name).ToList().Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
ViewBag.Roles = list;
return View();
}`
I'm guessing you do have some sort of TenantId or CompanyId that is an identifier to the tenant you are working with.
IdentityRole and IdentityUser are framework objects that is recommended to inherit to add your own properties. You should do just that:
public class MyApplicationRole : IdentityRole
{
public int CompanyId { get; set; }
}
public class MyApplicationuser : IdentityUser
{
public int CompanyId { get; set; }
}
You can also add reference to a company object here and link it as a foreign key. This might make your life easier retrieving company objects related to the user.
Based on the objects above I'll try answering your questions:
When roles are created you can append a CompanyId to the name as a prefix. Something like <CompanyId>#CustomerServiceRole. This will avoid name clashes. At the same time add CompanyId to the MyApplicationRole class. Prefixed identifier will avoid clashes, identifier in the object will make the roles discoverable by the company. Alternatively you can implement RoleValidator and do the validation based on unique role name within a company. But then you will have to change the code that creates user identity when users are logged in, as role ids are not stored into the cookie.
I think this is self explanatory from the previous answer:
context.Roles.Add(new Microsoft.AspNet.Identity.EntityFramework.IdentityRole()
{
CompanyId = companyId, // TODO Get the company Id
Name = companyId.ToString() + "#" + form["RoleName"]
});
Self explanatory, see code snippets above.
Depending how you link your roles to companies the query will be different. Basically you'll need to do a join between companies and matching roles based on CompanyId and then filter companies by the non-profit flag.
I am trying to store a whole truckload of information about a user from a webservice. As this is information about the currently authenticated user, I thought it would make sense to store that information in a custom IIdentity implementation.
The custom MagicMembershipProvider.GetUser(string id, bool userIsOnline) calls the webservice and returns a MagicMembershipUser instance with all the fields populated (department, phone number, other employee info).
The custom membership provider and custom membership user both work fine.
What and where is the best way to put the membership user information into the IPrincipal User object that is accessible in every controller?
I have been trying to wrap my brain around the program flow of security with IIdentity, IPrincipal and Role authorization in an MVC2 application -- but I'm really struggling here and could use some mentoring. There a Internet Ton of articles about the parts, but not much about the whole.
Edit
My best guess so far is to assign the HttpContext.Current.User in the FormsAuthenticationService:
public void SignIn(string userName, bool createPersistentCookie)
{
if (String.IsNullOrEmpty(userName))
throw new ArgumentException("Value cannot be null or empty.", "userName");
try
{
FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
MagicMembershipUser magicUser = _provider.GetUser("", false)
as MagicMembershipUser;
MagicIdentity identity = new MagicIdentity(userName, magicUser);
GenericPrincipal principal = new GenericPrincipal(identity, null);
HttpContext.Current.User = principal;
}
catch (Exception)
{
throw;
}
}
What and where is the best way to put the membership user information into the IPrincipal User object that is accessible in every controller?
In a custom [Authorize] filter implementation. You could override the AuthorizeCore method and call the base method and if it returns true query your membership provider and inject the custom magic identity into the context.
Example:
public class MagicAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (isAuthorized)
{
var username = httpContext.User.Identity.Name;
var magicUser = _provider.GetUser(username, false) as MagicMembershipUser;
var identity = new MagicIdentity(username, magicUser);
var principal = new GenericPrincipal(identity, null);
httpContext.User = principal;
}
return isAuthorized;
}
}
Now all that's left is decorate your base controller with the [MagicAuthorize] attribute.