Setup a route {tenant}/{controller}/{action}/{id} with ASP.NET MVC? - 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.

Related

Custom Authentication .Net MVC

I have a web app that allows a user to pay for a holiday booking.
The user will log in using a combination of a unique booking ID and a booking password.
Is there a simple way I can authenticate the user once they have successfully logged in. Is it a good idea to use the .net session object for this purpose?
The app wont be load balanced so I don't need to worry about storing session across different machines.
I would like to not have to use .Net membership as I don't want to create any extra tables in the database.
If you are able to retrieve the user, I guess this is possible. Below is the way I would try:
public ActionResult UrlAuth(string bookingId, string bookingPass)
{
var userManager=HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
// Retrieve the user by the give booking values (Custom part in your app
string userId= _bookingRepository.GetUserIdByBooking(bookingId, bookinggPass);
var user = userManager.FindById(userId);
if (user != null)
{
var userIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
HttpContext.GetOwinContext().Authentication.SignIn(new AuthenticationProperties { IsPersistent = false }, userIdentity );
// authentication succeed do what you want
return Redirect("Wherever");
}
ModelState.AddModelError("", "Invalid booking values");
return View();
}
Here's a good general article with custom authentication for MVC
http://www.ryadel.com/en/http-basic-authentication-asp-net-mvc-using-custom-actionfilter/
http://www.codeproject.com/Articles/1005485/RESTful-Day-sharp-Security-in-Web-APIs-Basic

Single authentication pipeline for webapi, mvc and signalr supporting basic and forms

My current services are using MVC to render forms, WebApi to move my viewModels back and forth and signalR for push notifications etc.
If the users are browsing the website they will be using forms auth, but we're introducing some mobile apps and I would like to be able to consume webapi and signalr from the mobile apps using basic auth, without having to maintain two separate sets of controllers.
I have two IPrincipals, a SessionPrincipal and a BasicPrincipal (where Session Principal inherits BasicPrincipal and has some additional contextual data). The idea is that some controllers will require to be on the website (SessionPrincipal), but everything else can be accessed by both web and mobile users (Basic Principal). Some won't require any authorisation at all, so can't just deny the request.
My current approach does the following steps to achieve this (some code omitted for brevity)
Global.asax Application_AuthenticateRequest
var cultureCookie = Request.Cookies["Culture"];
// Set culture ...
var authHeader = Request.Headers["Authorization"];
if (authHeader != null && authHeader.StartsWith("Basic"))
{
//Check Username / Password. If okay...
HttpContext.Current.User = new BasicAuthPrincipal(user);
}
else
{
var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
// Try and resolve Session from encrypted forms auth data. If okay...
HttpContext.Current.User = new SessionAuthPrincipal(Id, User, Agent);
}
}
Individual Authorize Filters (SessionMVC, SessionApi, BasicApi) that basically boil down to:
return HttpContext.Current.User as SessionPrincipal != null;
// Or
return HttpContext.Current.User as BasicPrincipal != null;
So if they were successfully set in global.asax then proceed to the controller.
Now, I have a working implementation of this, so why am I asking for help?
I'm not sure of certain fringe scenarios that may upset this. Am I asking for trouble for implementing it this way?
I read about HttpContext not being thread safe, but Application_AuthenticateRequest should run before everything else and no further changes are made to that data so I think it should be okay.

Use synced Active Directory in Azure to validate users including groups?

I am porting an application to azure and in that app we use Active Directory to authenticate users like the following:
var user = model.UserName.Split('\\');
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, user[0]))
{
if (pc.ValidateCredentials(user[1], model.Password, ContextOptions.Negotiate))
{
using (var adUser = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, user[1]))
{
if (!MembershipService.ValidateUser(model.UserName, model.Password))
{
using (var userDb = new UsersDbContext())
{
if (userDb.aspnet_Users.Count(u => u.UserName.ToLower().Contains(model.UserName)) <= 0)
MembershipService.CreateUser(model.UserName, model.Password, adUser.EmailAddress);
else
{
var msUser = Membership.GetUser(model.UserName);
msUser.ChangePassword(msUser.ResetPassword(), model.Password);
}
}
}
FormsService.SignIn(model.UserName, model.RememberMe);
foreach (var role in Roles.GetAllRoles())
{
using (var group = GroupPrincipal.FindByIdentity(pc, role))
{
if (group != null)
{
if (adUser.IsMemberOf(group))
{
if (!Roles.IsUserInRole(model.UserName, role))
Roles.AddUserToRole(model.UserName, role);
}
else
{
if (Roles.IsUserInRole(model.UserName, role))
Roles.RemoveUserFromRole(model.UserName, role);
}
}
}
}
}
}
}
This works fine on our web-server which is connected to our domain server.
Now I set up an Windows Azure Active Directory and configured it to be synced with our On-Premise AD which also works.
But I am now struggeling on finding a way to connect my PrincipalContext to the WAAD.
Is this even possible and how? If not, what is the alternative?
I only found examples using Single-Sign-On which does this redirection to the MS login page we do NOT want to use, because we have a mixed authentication and depending on the entered user name it either uses the ASP.NET Membership or pulls the user and groups from AD (and actually creates an ASP.NET Membership user as seen above).
No.
You can't really use PrincipalContext with WAAD. Have to explicitly state here that you cannot currently (Jan. 2014) do direct user authentication against WAAD. You will need to rewrite some parts of your application to be compatible:
Authentication happens only on the WAAD side, your code cannot do user+password validation. This also happens on WAAD provided login page. You have limited control on how this page looks like and can customize it via Premium features of WAAD.
You can create users and reset user password using the WAAD Graph API.
Explore the Graph API for additional operations you might need (i.e. ask for user's group membership, direct reports, etc.)
You will have to switch from Windows Authentication to Federated Authentication. Depending on what VS version you are using this might be easy or tough. For VS 2012 there is Identity and Access Tool extension. While in 2013 authentication can only be configured when you create the project and cannot be altered afterwards. But you can copy configuration changes from other project over. You need changes in web.config file along with what is initialized in global.asax. Check it here - although about VS 2013 RC, the process is same in RTM.

How do I get Active Directory group id for authorized user

I have web application that uses the Authorize attribute with roles specified to restrict access to some pages:
[Authorize(Roles = "AD_group1, AD_group2")]
The question is - is there any way I can get some kind of an Active Directory groupId for authorized user (no matter int or string)?
upd:
Basic idea is to store some table in database, containing templates which should be separate for every group. e.g. users in group1 can have some templates for fast answer to typical questions while group2 doesn't have any of them, or have some other templates
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, "SomeUserName");
// or if you want the currently logged in user - you can also use:
// UserPrincipal user = UserPrincipal.Current;
if(user != null)
{
// get all groups the user is a member of
foreach(GroupPrincipal group in user.GetAuthorizationGroups())
{
string distinguishedName = group.DistinguishedName;
Guid groupGuid = group.Guid;
}
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!

Windows authentication & SQL Membership services

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;
}
}
}

Resources