Windows authentication & SQL Membership services - asp.net-mvc

I have an ASP.Net MVC intranet site which uses Windows Authentication to know who is logged in (no anon browsing allowed). The first time the users visit, I collect some very basic information from them for their Contact object (such as name, email, country) which is then stored in the apps database.
I want to make the site role based, so I need to be able to assign each user a role (user, admin etc). I could do this using ADS groups, but this seems rather heavyweight. Can I use the SQL Membership services provided by ASP.Net to store their usernames and then the roles they belong to, or will I be forced to collect passwords etc (defeating the point of using Windows Authentication)? Also does this integrate with the ASP.Net MVC [Authorize] attribute?

It is certainly the case in "normal" ASP.NET that you can use this combination (Windows authentication and SQL for Roles), so it should be possible for MVC too.
Here's a link that might help.

Yes, you can do this.
Authorize uses the IsInRole method of IPrincipal to determine if the user is within a given role.
You can switch out the default implementation of IPrincipal during the AuthenticateRequest event within Global.asax with your implementation that handles this your way.
Here's some sample code that might actually work and compile and not expose your website to attacks by hackers:
private void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (!Request.IsAuthenticated)
{
Context.User = new MyPrincipal { Identity = new MyIdentity
{ Type = UserType.Inactive, Id = int.MinValue }};
Thread.CurrentPrincipal = Context.User;
}
else
{
HttpCookie authCookie = Request.Cookies[
FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket =
FormsAuthentication.Decrypt(authCookie.Value);
var identity = Db.GetIdentity(
authTicket.Name, new HttpRequestWrapper(Request));
Context.User = new MyPrincipal { Identity = new MyIdentity
{ Type = UserType.Inactive, Id = int.MinValue }};
Thread.CurrentPrincipal = Context.User;
}
}
}

Related

MVC give authorization on login to access actions

I've been running in circles trying to find an answer to this, and I can't seem to make any progress.
All I want to do, is check for a correct username and password combo, then GIVE the user authorization to access MVC actions decorated with the [Authorize] tag.
public ActionResult DoLogin(PageInitModel model)
{
EF_db db = new EF_db();
string saltPassword = getSaltedPasswordEncryption(model.UserName, model.Password);
var user = (from s in db.Users
where s.Username == model.UserName
&& s.Password == saltPassword
select s).FirstOrDefault();
if(user == null)
{
model.LoginFail = true;
return View("Login", model);
}
else
{
//
// give the user some magical token to access [Authorize] actions here
//
return RedirectToAction("Index", "Menu");
}
}
Above is the login action (called from a basic form), and below would be one of the actions I would like to restrict access to:
public class MenuController : Controller
{
[Authorize]
public ActionResult Index()
{
var pageInitModel = new PageInitModel();
return View("Menu",pageInitModel);
}
}
I would like to keep track of the users myself (in my own tables), because there are many additional attributes I would like to track. I'm not sure if I need to write a custom AuthorizeAttribute, or what, but I can't seem to make any headway.
Any help pointing me in the right direction is greatly appreciated.
Thanks!
You should look into ASP.NET Identity. Note this was introduced in ASP.NET 5 (?) and replaces some older frameworks Microsoft had like Basic Membership etc.
Honestly you really don't want to roll your own. ASP.NET Identity does exactly what you describe right out of the box. Keep in mind there are two distinct concepts Authentication and Authorization.
Authentication is verifying the user is who he says he is.
Authorization is restricting access to only users who are "allowed".
There are many ways to structure Authorization but I assume Role based Authorization will meet your need. You will define multiple roles, say User, Admin, Moderator, Admin, etc.
You then restrict access to actions based on the role. You can even make roles overlap and allow a single user to have multiple roles. The end result is that once a user logs in their role determines what they can do via the authorize tags.
[Authorize(Roles="Admin")]
If the user is not logged in, they will be redirected to the login form to AUTHENTICATE ("Um I don't know who you are". Once authenticated if they are not authorized they will still be restricted from this action ("Oh I know who you are but you are not allowed to do this".
To round it out you can also have anonymous actions which mean no authentication is required and Authorize actions which are not limited to a specific role (any authenticated user is allowed but not unauthenticated users).
The fact that even with a basic [Authorize] you are having issues leads be to believe there is some configuration problems in even the Authentication. I recommend going through a tutorial building an example app like this one:
http://blogs.msdn.com/b/webdev/archive/2013/10/20/building-a-simple-todo-application-with-asp-net-identity-and-associating-users-with-todoes.aspx
As I understood you have your users information and you may want to use the authentication by custom code. But to actually login the user you must create a session of the user (Cookie session) for further authentication of requests.
You have already verified the username and password. now you've to create a session cookie using form authentication. Here's answer to your first problem:
// give the user some magical token to access [Authorize] actions here
// User name and password matches
if (userValid)
{
FormsAuthentication.SetAuthCookie(username, false);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
The Authorize attribute will then check if the request coming for the action is authenticated. This attribute internally checks if a session cookie is valid and authenticated.
Also this attribute not only checks for authentication but also can verify authorization. So you can use custom roles as well with Authorize attribute to restrict users to accessing specific views/actions.
It was pointed out to me that I needed to look into cookies or existing membership providers, so I looked around in FormsAuthenticationTickets and rolled my own solution that I would love feedback on.
I ended up creating a new ticket when the login was successful with the FormsAuthenticationTicket class (see below).
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
user.Username,
DateTime.Now,
DateTime.Now.AddHours(2),
false,
userData,
FormsAuthentication.FormsCookiePath);
// Encrypt the ticket.
string encTicket = FormsAuthentication.Encrypt(ticket);
// Create the cookie.
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
Then I wrote a custom ActionFilter, so I could make my own method decorators (or whatever they're called).
namespace MyApp.Filters
{
public class CustomLoginFilter : ActionFilterAttribute, IActionFilter
{
void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
{
// Retrieves the cookie that contains your custom FormsAuthenticationTicket.
HttpCookie authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
// There's actually a cookie
// Decrypts the FormsAuthenticationTicket that is held in the cookie's .Value property.
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket != null && user != null && data == authTicket.UserData)
{
// Everything looks good
this.OnActionExecuting(filterContext);
}
else
{
//log bounceback - someone screwed with the cookie
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", "Login" },
{ "action", "Index" }
});
}
}
else
{
//log bounceback - not logged in
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", "Login" },
{ "action", "Index" }
});
}
}
}
}
I'll likely play with it more, but it seems to work fine for now.
Thanks for the help.

ASP.net MVC Authentication using external PHP API

I'm developing an asp.net MVC website with the following requirements:
Develop pages for Admin and Users, these pages must be accessed
based on logged in user role: Admin or User
The website supports login only, You will call a PHP API which resides on an external website, it returns a JSON as a result that includes id, username, and role (admin, user)
You may save the result of returned json on a session to be used in your pages but this data must disappear after logout or session expiration.
I know how to develop the calling HTTP stuff and processing json, but I'm not familiar with authorization and authentication stuff, nor with using membership providers, I searched a lot and at first I thought of using SimpleMembership but I found that won't work since it depends on SQL queries and in my case I'm not going to use any type of databases.
I heard about asp.net identity but I'm not sure how to use it or if it's for my case or not, I searched again and I couldn't find any resource to help me achieve authentication and authorization for my case
I'm asking for your help to help me out and point me in the right direction
Thank you for your help
There is an example of using OAuth separated http auth API:
http://www.asp.net/web-api/overview/security/external-authentication-services
Yes, this example depends on some specified http API..
But in case when you have some another JSON/XML RPC API you can try to create your own feature like a:
public class ExternalAuthAPIClient {
public User Auth(string username, string password) { .... }
}
And use it in your AuthController in the method Login
BUT! This approach requires a lot of side changes.. where to store your user.. then create custom AuthenticateAttribure ... etc.
The better solution is to create oAuth supported API on your PHP side and use it with ASP.NET Identity.
I finally found a solution,I didn't need to use any membership providers since my website supports only login and via an API,I wrote the following code,this one is in AccountController :
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel login, string returnUrl)
{
if (!ModelState.IsValid)
{
ViewBag.Error = "Form is not valid; please review and try again.";
return View(login);
}
//Call external API,check if credentials are valid,set user role into userData
string userData="Admin";
var ticket = new FormsAuthenticationTicket(
version: 1,
name: login.Username,
issueDate: DateTime.Now,
expiration: DateTime.Now.AddSeconds(HttpContext.Session.Timeout),
isPersistent: false,
userData: userData);
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
HttpContext.Response.Cookies.Add(cookie);
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", userData);
}
Then decorate admin/user controller with Authorize attribute like this:
[Authorize(Roles = "admin")]
public class AdminController : Controller
Then add the following code in Global.asax :
public override void Init()
{
base.PostAuthenticateRequest += Application_PostAuthenticateRequest;
}
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
var decodedTicket = FormsAuthentication.Decrypt(cookie.Value);
var roles = decodedTicket.UserData;
var principal = new GenericPrincipal(HttpContext.Current.User.Identity, roles);
HttpContext.Current.User = principal;
}
}

CurrentPrincipal/User is empty in Web API service

I may be missing something obvious here. I'm new to both MVC and Web API, so I'm working on keeping my head above water.
I have an MVC application that interfaces with a Web API service. Authentication will be handled by a login service developed internally. When working, the MVC client should check if the current user is authenticated. If they're not, then it will redirect to this login service, which is supposed to authenticate the user and update the current user. I then need to be able to access this identity from the Web API service.
I'm operating under the assumption that the current principal (set via Thread.CurrentPrincipal or HTTPContext.Current.User) in the MVC application should be available in my Web API service, but whenever I try to access it from the service, the principal is empty. I've tried accessing the principal from the service using all of the following options, but it's always empty:
RequestContext.Principal
User.Identity
HttpContext.Current.User
Thread.CurrentPrincipal
Here's the basic idea of my code:
MVC Controller:
public ActionResult Index() {
//Just create a test principal here to see if it's available in the service
IPrincipal temp = new GenericPrincipal(new GenericIdentity("myUserName"), new string[]{});
Thread.CurrentPrincipal = temp;
using (var client = new HttpClient()) {
client.BaseAddress = new Uri("myServiceAddress");
HttpResponseMessage response = client.GetAsync("resourceString")).Result;
...Code to deal with result
}
}
Web API Controller:
[HttpGet]
public HttpResponseMessage MyAction() {
if (User.Identity == null || !User.Identity.IsAuthenticated) {
//So sad
} else {
//Do some work
}
}
The current principal is always empty, regardless of how I try to access it.
I think that you're going to need to set both the thread and context principal. Here's what I'm doing:
private static void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null) {
HttpContext.Current.User = principal;
}
}
Part way down This Article it says:
If your application performs any custom authentication logic, you must set the principal on two places:
Thread.CurrentPrincipal. This property is the standard way to set the thread's principal in .NET.
HttpContext.Current.User. This property is specific to ASP.NET.

Extending Windows Authentication in ASP.NET MVC 3 Application

after a lot of googling and reading several solutions on how to manage mixed mode authentication in ASP.NET apps, I still have no fitting solution for my problem.
I've got to implement an intranet application for a bunch of different user groups. Until now i've used windows authenthication which was very simple to implement. My problems arise when it comes to authorizing usergroups for special application functionalities.
Using [Authorize(Users = "DOMAIN\\USER")] works great but due to that i have no access to the active directory managament, it is impossible to me to configure rolemanagement in the way I need it for my application.
What I'd like to do is defining custom roles and memberships in addition to the ones that are defined within the active directory (is such an extension possible? e.g. by implementing an own membershipprovider?).
What do you think is the best solution for my problem. Do I really have to implement a complex mixed mode authentication with forms authentication in addition to windows authentication?
Used Technologies:
MS SQL Server 2008
MS VS 2010
ASP.NET MVC 3 - Razor View Engine
Telerik Extensions for ASP.NET MVC
IIS 7 on Windows Server 2008
EDIT (final solution thanks to the help of dougajmcdonald):
After pointing me to use a custom IPrincipal implementation I've found some solutions here and here. Putting everything together I came to the following solution:
1.Create a custom principal implementation:
public class MyPrincipal: WindowsPrincipal
{
List<string> _roles;
public MyPrincipal(WindowsIdentity identity) : base(identity) {
// fill roles with a sample string just to test if it works
_roles = new List<string>{"someTestRole"};
// TODO: Get roles for the identity out of a custom DB table
}
public override bool IsInRole(string role)
{
if (base.IsInRole(role) || _roles.Contains(role))
{
return true;
}
else
{
return false;
}
}
}
2.Integrate my custom principal implementation into the application through extending the "Global.asax.cs" file:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (Request.IsAuthenticated)
{
WindowsIdentity wi = (WindowsIdentity)HttpContext.Current.User.Identity;
MyPrincipal mp = new MyPrincipal(wi);
HttpContext.Current.User = mp;
}
}
3.Use my custom roles for authorization in my application
public class HomeController : Controller
{
[Authorize(Roles= "someTestRole")]
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
}
It works!!! yeah!
I'm not sure if this still applies in MVC, but in Webforms one way to do this would be as follows:
Create a new IPrincipal implementation perhaps extending WindowsPrincipal
In this class, give it a collection of roles (your own custom roles)
Populate those roles, by perhaps getting them from the DB.
Override IsInRole to return true if the role provided is EITHER true from the base call (WindowsAuthentication/Role) OR from your own custom role collection.
This way you can still hook into Principal.IsInRole("MyRole") and also the principal [PrincipalPermission()] annotation.
Hope it helps.
EDIT in answer to q's:
To integrate the principal into the authorisation you need to write your own method for OnAuthenticate in the global.asax for the type of authentication, so I would guess for you, something like this:
void WindowsAuthentication_OnAuthenticate(object sender, WindowsAuthenticationEventArgs e)
{
// ensure we have a name and made it through authentication
if (e.Identity != null && e.Identity.IsAuthenticated)
{
//create your principal, pass in the identity so you know what permissions are tied to
MyCustomePrincipal opPrincipal = new MyCustomePrincipal(e.Identity);
//assign your principal to the HttpContext.Current.User, or perhaps Thread.Current
HttpContext.Current.User = opPrincipal;
}
}
I believe Authorize came in at a later date to the PrincipalPermission, but I'm not too sure as to when/why of the differences I'm afraid :( - sorry!

Setup a route {tenant}/{controller}/{action}/{id} with ASP.NET MVC?

I would like to setup a multi-tenant ASP.NET MVC app. Ideally, this app would have a route with {tenant}/{controller}/{action}/{id}, each tenant representing an logical instance of the app (simply independent multi-user accounts)
The fine grained details how do that are still quite unclear to me. Any guide available to setup such multi-tenant scheme with ASP.NET MVC?
I am currently working on a similar project using ASP.Net MVC, Forms Authentication and the SQL providers for Membership/Roles/Profile. Here is the approach I am taking:
Register the default route as `{tenant}/{controller}/{action}/{id}
Change the default behavior of the FormsAuthenticationService that comes with the standard MVC template. It should set the UserData of the authentication ticket to include the tenant name (from your route).
public void SignIn(string userName, bool createPersistentCookie, string tenantName)
{
var ticket = new FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.AddMinutes(30),
createPersistentCookie, tenantName);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
HttpContext.Current.Response.AppendCookie(cookie);
}
In your global.asax file to do some tenant security checking and allow partioning of users between tenants in one membership database
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
//Since this method is called on every request
//we want to fail as early as possible
if (!Request.IsAuthenticated) return;
var route = RouteTable.Routes.GetRouteData(new HttpContextWrapper(Context));
if (route == null || route.Route.GetType().Name == "IgnoreRouteInternal") return;
if (!(Context.User.Identity is FormsIdentity)) return;
//Get the current tenant specified in URL
var currentTenant = route.GetRequiredString("tenant");
//Get the tenant that that the user is logged into
//from the Forms Authentication Ticket
var id = (FormsIdentity)Context.User.Identity;
var userTenant = id.Ticket.UserData;
if (userTenant.Trim().ToLower() != currentTenant.Trim().ToLower())
{
//The user is attempting to access a different tenant
//than the one they logged into so sign them out
//an and redirect to the home page of the new tenant
//where they can sign back in (if they are authorized!)
FormsAuthentication.SignOut();
Response.Redirect("/" + currentTenant);
return;
}
//Set the application of the Sql Providers
//to the current tenant to support partitioning
//of users between tenants.
Membership.ApplicationName = currentTenant;
Roles.ApplicationName = currentTenant;
ProfileManager.ApplicationName = currentTenant;
}
Partition each tenants data. Here are two options:
4a. Use a separate database for each tenant. This provides the best data security for your tenants. In the shared membership database, add a table that is keyed on unique appid for each tenant and use this table to store and retrieve the connection string based on the current tenant.
4b. Store all data in one database and key each table on the unique tenant id. This provides slightly less data security for your tenants but uses only one SQL Server license.
You will prob find these links useful.

Resources