ASP.NET MVC Forms Authentication + Authorize Attribute + Simple Roles - asp.net-mvc

I'm trying to add simple Authentication and Authorization to an ASP.NET MVC application.
I'm just trying to tack on some added functionality to the basic Forms Authentication (due to simplicity and custom database structure)
Assuming this is my database structure:
User:
username
password
role (ideally some enum. Strings if need be. Currently, user only has ONE role, but this might change)
High Level Problem:
Given the above database structure, I would like to be able to do the following:
Simple Login using Forms Authentication
Decorate my actions with:
[Authorize(Roles={ MyRoles.Admin, MyRoles.Member})]
Use roles in my Views (to determine links to display in some partials)
Currently, all I'm really sure of is how to Authenticate. After that I'm lost. I'm not sure at which point do I grab the user role (login, every authorization?). Since my roles may not be strings, I'm not sure how they will fit in with the User.IsInRole().
Now, I'm asking here because I haven't found a "simple" accomplish what I need. I have seen multiple examples.
For Authentication:
We have simple user validation that checks the database and "SetAuthCookie"
Or we override the Membership provider and do this inside of ValidateUser
In either of these, I'm not sure how to tack on my simple user Roles, so that they work with the:
HttpContext.Current.User.IsInRole("Administrator")
Furthermore, I'm not sure how to modify this to work with my enum values.
For Authorization, I've seen:
Deriving AuthorizeAttribute and implementing AuthorizeCore OR OnAuthorization to handle roles?
Implementing IPrincipal?
Any assistance would be greatly appreciated. However, I fear I may need a lot of detail, because none of what I've Googled seems to fit with what I need to do.

I think I've implemented something similar.
My solution, based on NerdDinner tutorial, is following.
When you sign the user in, add code like this:
var authTicket = new FormsAuthenticationTicket(
1, // version
userName, // user name
DateTime.Now, // created
DateTime.Now.AddMinutes(20), // expires
rememberMe, // persistent?
"Moderator;Admin" // can be used to store roles
);
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
System.Web.HttpContext.Current.Response.Cookies.Add(authCookie);
Add following code to Global.asax.cs:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null || authCookie.Value == "")
return;
FormsAuthenticationTicket authTicket;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch
{
return;
}
// retrieve roles from UserData
string[] roles = authTicket.UserData.Split(';');
if (Context.User != null)
Context.User = new GenericPrincipal(Context.User.Identity, roles);
}
After you've done this, you can use [Authorize] attribute in your controller action code:
[Authorize(Roles="Admin")]
public ActionResult AdminIndex ()
Please let me know if you have further questions.

Build a custom AuthorizeAttribute that can use your enums rather than strings. When you need to authorise, convert the enums into strings by appending the enum type name + the enum value and use the IsInRole from there.
To add roles into an authorised user you need to attach to the HttpApplication AuthenticateRequest event something like the first code in http://www.eggheadcafe.com/articles/20020906.asp ( but invert the massively nested if statements into guard clauses!).
You can round-trip the users roles in the forms auth cookie or grab them from the database each time.

I did something like this:
Use the Global.asax.cs to load the roles you want to compare in session,cache, or application state, or load them on the fly on the ValidateUser controller
Assign the [Authorize] attribute to your controllers, you want to require authorization for
[Authorize(Roles = "Admin,Tech")]
or to allow access, for example the Login and ValidateUser controllers use the below attribute
[AllowAnonymous]
My Login Form
<form id="formLogin" name="formLogin" method="post" action="ValidateUser">
<table>
<tr>
<td>
<label for="txtUserName">Username: (AD username) </label>
</td>
<td>
<input id="txtUserName" name="txtUserName" role="textbox" type="text" />
</td>
</tr>
<tr>
<td>
<label for="txtPassword">Password: </label>
</td>
<td>
<input id="txtPassword" name="txtPassword" role="textbox" type="password" />
</td>
</tr>
<tr>
<td>
<p>
<input id="btnLogin" type="submit" value="LogIn" class="formbutton" />
</p>
</td>
</tr>
</table>
#Html.Raw("<span id='lblLoginError'>" + #errMessage + "</span>")
</form>
Login Controller and ValidateUser controller invoked from the Form post
Validate user is authentication via a WCF service that validates against the Windows AD Context local to the service, but you can change this to your own authentication mechanism
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using System.Security.Principal;
using MyMVCProject.Extensions;
namespace MyMVCProject.Controllers
{
public class SecurityController : Controller
{
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
Session["LoginReturnURL"] = returnUrl;
Session["PageName"] = "Login";
return View("Login");
}
[AllowAnonymous]
public ActionResult ValidateUser()
{
Session["PageName"] = "Login";
ViewResult retVal = null;
string loginError = string.Empty;
HttpContext.User = null;
var adClient = HttpContext.Application.GetApplicationStateWCFServiceProxyBase.ServiceProxyBase<UserOperationsReference.IUserOperations>>("ADService").Channel;
var username = Request.Form["txtUserName"];
var password = Request.Form["txtPassword"];
//check for ad domain name prefix
if (username.Contains(#"\"))
username = username.Split('\\')[1];
//check for the existence of the account
var acctReq = new UserOperationsReference.DoesAccountExistRequest();
acctReq.userName = username;
//account existence result
var accountExist = adClient.DoesAccountExist(acctReq);
if (!accountExist.DoesAccountExistResult)
{
//no account; inform the user
return View("Login", new object[] { "NO_ACCOUNT", accountExist.errorMessage });
}
//authenticate
var authReq = new UserOperationsReference.AuthenticateRequest();
authReq.userName = username;
authReq.passWord = password;
var authResponse = adClient.Authenticate(authReq);
String verifiedRoles = string.Empty;
//check to make sure the login was as success against the ad service endpoint
if (authResponse.AuthenticateResult == UserOperationsReference.DirectoryServicesEnumsUserProperties.SUCCESS)
{
Dictionary<string, string[]> siteRoles = null;
//get the role types and roles
if (HttpContext.Application["UISiteRoles"] != null)
siteRoles = HttpContext.Application.GetApplicationState<Dictionary<string, string[]>>("UISiteRoles");
string groupResponseError = string.Empty;
if (siteRoles != null && siteRoles.Count > 0)
{
//get the user roles from the AD service
var groupsReq = new UserOperationsReference.GetUsersGroupsRequest();
groupsReq.userName = username;
//execute the service method for getting the roles/groups
var groupsResponse = adClient.GetUsersGroups(groupsReq);
//retrieve the results
if (groupsResponse != null)
{
groupResponseError = groupsResponse.errorMessage;
var adRoles = groupsResponse.GetUsersGroupsResult;
if (adRoles != null)
{
//loop through the roles returned from the server
foreach (var adRole in adRoles)
{
//look for an admin role first
foreach (var roleName in siteRoles.Keys)
{
var roles = siteRoles[roleName].ToList();
foreach (var role in roles)
{
if (adRole.Equals(role, StringComparison.InvariantCultureIgnoreCase))
{
//we found a role, stop looking
verifiedRoles += roleName + ";";
break;
}
}
}
}
}
}
}
if (String.IsNullOrEmpty(verifiedRoles))
{
//no valid role we need to inform the user
return View("Login", new object[] { "NO_ACCESS_ROLE", groupResponseError });
}
if (verifiedRoles.EndsWith(";"))
verifiedRoles = verifiedRoles.Remove(verifiedRoles.Length - 1, 1);
//all is authenticated not build the auth ticket
var authTicket = new FormsAuthenticationTicket(
1, // version
username, // user name
DateTime.Now, // created
DateTime.Now.AddMinutes(20), // expires
true, // persistent?
verifiedRoles // can be used to store roles
);
//encrypt the ticket before adding it to the http response
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
Response.Cookies.Add(authCookie);
Session["UserRoles"] = verifiedRoles.Split(';');
//redirect to calling page
Response.Redirect(Session["LoginReturnURL"].ToString());
}
else
{
retVal = View("Login", new object[] { authResponse.AuthenticateResult.ToString(), authResponse.errorMessage });
}
return retVal;
}
}
}
User is authenticated now create the new Identity
protected void FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs e)
{
if (FormsAuthentication.CookiesSupported == true)
{
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null || authCookie.Value == "")
return;
FormsAuthenticationTicket authTicket = null;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch
{
return;
}
// retrieve roles from UserData
if (authTicket.UserData == null)
return;
//get username from ticket
string username = authTicket.Name;
Context.User = new GenericPrincipal(
new System.Security.Principal.GenericIdentity(username, "MyCustomAuthTypeName"), authTicket.UserData.Split(';'));
}
}
On my site at the the top of my _Layout.cshtml I have something like this
{
bool authedUser = false;
if (User != null && User.Identity.AuthenticationType == "MyCustomAuthTypeName" && User.Identity.IsAuthenticated)
{
authedUser = true;
}
}
Then in the body
#{
if (authedUser)
{
<span id="loggedIn_userName">
<label>User Logged In: </label>#User.Identity.Name.ToUpper()
</span>
}
else
{
<span id="loggedIn_userName_none">
<label>No User Logged In</label>
</span>
}
}

Add your users to the table "users in roles". Use the stored procedure "addusertorole" (something like that) in your code to add to various roles. You can create the roles very simply in the "roles" table.
Your tables to use: User, UsersInRole, Roles
Use the built in Stored Procs to manipulate those tables. Then all you have to do is add the attribute.
For example you can have an "Admin" attribute on a view that selects a user and adds them to a role. You can use the stored proc to add that user to the role.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using SISWEBBSI.Models.Model;
using SISWEBBSI.Models.Model.Entities;
using SISWEBBSI.Models.ViewModel;
namespace SISWEBBSI.Controllers.ActionFilter
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class RequerAutorizacao : ActionFilterAttribute
{
public Grupo.Papeis[] Papeis = {} ;
public string ViewName { get; set; }
public ViewDataDictionary ViewDataDictionary { get; set; }
public AcessoNegadoViewModel AcessoNegadoViewModel { get; set; }
public override void OnActionExecuting(ActionExecutingContext FilterContext)
{
if (!FilterContext.HttpContext.User.Identity.IsAuthenticated)
{
string UrlSucesso = FilterContext.HttpContext.Request.Url.AbsolutePath;
string UrlRedirecionar = string.Format("?ReturnUrl={0}", UrlSucesso);
string UrlLogin = FormsAuthentication.LoginUrl + UrlRedirecionar;
FilterContext.HttpContext.Response.Redirect(UrlLogin, true);
}
else
{
if (Papeis.Length > 0)
{
//Papel ADMINISTRADOR sempre terá acesso quando alguma restrição de papeis for colocada.
int NovoTamanho = Papeis.Count() + 1;
Array.Resize(ref Papeis, NovoTamanho);
Papeis[NovoTamanho - 1] = Grupo.Papeis.ADMINISTRADOR;
UsuarioModel Model = new UsuarioModel();
if (!Model.UsuarioExecutaPapel(FilterContext.HttpContext.User.Identity.Name, Papeis))
{
ViewName = "AcessoNegado";
String Mensagem = "Você não possui privilégios suficientes para essa operação. Você deve estar nos grupos que possuem";
if(Papeis.Length == 1)
{
Mensagem = Mensagem + " o papel: <BR/>";
}
else if (Papeis.Length > 1)
{
Mensagem = Mensagem + " os papéis: <BR/>";
}
foreach (var papel in Papeis)
{
Mensagem = Mensagem + papel.ToString() + "<br/>";
}
AcessoNegadoViewModel = new AcessoNegadoViewModel();
AcessoNegadoViewModel.Mensagem = Mensagem;
ViewDataDictionary = new ViewDataDictionary(AcessoNegadoViewModel);
FilterContext.Result = new ViewResult { ViewName = ViewName, ViewData = ViewDataDictionary };
return;
}
}
}
}
}
}

Related

Implement Active Directory login in an existing ASP.NET MVC 4.6 web project

I have to change the existing (Windows) login for my ASP.NET MVC + Knockout application with Active Directory authentication.
It consists of mvc controllers and webapi controllers. Both have to be authenticated.
I thought I would do this by changing to forms authentication and create a login page and when the users clicks login, query Active Directory with the System.DirectoryServices.DirectoryEntry.
Then the other processes like change password, register etc. would also get a custom html page and do their actions via the System.DirectoryServices.DirectoryEntry on our Active Directory.
(that is, I could not find any other way that people would do it, and I did find some who would do it like this, and it sounds the same like previous forms authentications I've seen. In this case the user/passwords would not be in a database table but in Active Directory. Same idea, swap database table by active directory).
To see how this would be on a brandnew project, I created a new ASP.NET MVC project and choose 'work- or school acounts' (which says 'for applications that authenticate users with active directory) and choose 'on premise'.
However, then I have to provide these items:
on-premises authority
app Id url
I don't know what to do with that. The only thing I have is an active directory url like ldap://etc..
Is this another/newer/better way of doing active directory login? Or the only right one (is forms authentication wrong?) or the wrong one?
I'm confused.
You can use the following approach in order to implement Active Directory Authentication in ASP.NET MVC.
Step 1: Modify the Login methods in the AccountController as shown below (also add the necessary references):
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
try
{
if (!ModelState.IsValid)
{
return View(model);
}
// Check if the User exists in LDAP
if (Membership.GetUser(model.UserName) == null)
{
ModelState.AddModelError("", "Wrong username or password");
return this.View(model);
}
ApplicationGroupManager groupManager = new ApplicationGroupManager();
// Validate the user using LDAP
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
// FormsAuthentication.SetAuthCookie(model.UserName, false);
// Check if the User exists in the ASP.NET Identity table (AspNetUsers)
string userName = model.UserName.ToString().ToLower(new CultureInfo("en-US", false)); // When UserName is entered in uppercase containing "I", the user cannot be found in LDAP
//ApplicationUser user = UserManager.FindByName(userName);
ApplicationUser user = await UserManager.FindByNameAsync(userName); //Asynchronous method
if (user == null) // If the User DOES NOT exists in the ASP.NET Identity table (AspNetUsers)
{
// Create a new user using the User data retrieved from LDAP
// Create an array of properties that we would like and add them to the search object
string[] requiredProperties = new string[] { "samaccountname", "givenname", "sn", "mail", "physicalDeliveryOfficeName", "title" };
var userInfo = CreateDirectoryEntry(model.UserName, requiredProperties);
user = new ApplicationUser();
// For more information about "User Attributes - Inside Active Directory" : http://www.kouti.com/tables/userattributes.htm
user.UserName = userInfo.GetDirectoryEntry().Properties["samaccountname"].Value.ToString();
user.Name = userInfo.GetDirectoryEntry().Properties["givenname"].Value.ToString();
user.Surname = userInfo.GetDirectoryEntry().Properties["sn"].Value.ToString();
user.Email = userInfo.GetDirectoryEntry().Properties["mail"].Value.ToString();
user.EmailConfirmed = true;
//user.PasswordHash = null;
//user.Department = GetDepartmentId(userInfo.GetDirectoryEntry().Properties["physicalDeliveryOfficeName"].Value.ToString());
//await Register(user);
var result = await UserManager.CreateAsync(user); //Asynchronous method
//If the User has succesfully been created
//if (result.Succeeded)
//{
// //var code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
// //var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
// //await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking this link: link");
// //ViewBag.Link = callbackUrl;
// //return View("DisplayEmail");
//}
// Define user group (and roles)
var defaultGroup = "751b30d7-80be-4b3e-bfdb-3ff8c13be05e"; // Id of the ApplicationGroup for the Default roles
//groupManager.SetUserGroups(newUser.Id, new string[] { defaultGroup });
await groupManager.SetUserGroupsAsync(user.Id, new string[] { defaultGroup }); //Asynchronous method
//groupManager.SetGroupRoles(newGroup.Id, new string[] { role.Name });
}
// !!! THERE IS NO NEED TO ASSIGN ROLES AS IT IS ASSIGNED AUTOMATICALLY IN ASP.NET Identity 2.0
//else // If the User exists in the ASP.NET Identity table (AspNetUsers)
//{
// //##################### Some useful ASP.NET Identity 2.0 methods (for Info) #####################
// //ApplicationGroupManager gm = new ApplicationGroupManager();
// //string roleName = RoleManager.FindById("").Name; // Returns Role Name by using Role Id parameter
// //var userGroupRoles = gm.GetUserGroupRoles(""); // Returns Group Id and Role Id by using User Id parameter
// //var groupRoles = gm.GetGroupRoles(""); // Returns Group Roles by using Group Id parameter
// //string[] groupRoleNames = groupRoles.Select(p => p.Name).ToArray(); // Assing Group Role Names to a string array
// //###############################################################################################
// // Assign Default ApplicationGroupRoles to the User
// // As the default roles are already defined to the User after the first login to the system, there is no need to check if the role is NULL (otherwise it must be checked!!!)
// //var groupRoles = groupManager.GetGroupRoles("751b30d7-80be-4b3e-bfdb-3ff8c13be05e"); // Returns Group Roles by using Group Id parameter
// var groupRoles = await groupManager.GetGroupRolesAsync("751b30d7-80be-4b3e-bfdb-3ff8c13be05e"); // Returns Group Roles by using Group Id parameter (Asynchronous method)
// foreach (var role in groupRoles)
// {
// //Assign ApplicationGroupRoles to the User
// string roleName = RoleManager.FindById(role.Id).Name;
// UserManager.AddToRole(user.Id, roleName);
// }
//}
//Sign in the user
await SignInAsync(user, model.RememberMe);
if (this.Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return this.Redirect(returnUrl);
//return RedirectToLocal(returnUrl);
}
return this.RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "Wrong username or password");
return this.View(model);
}
}
catch (Exception ex)
{
TempData["ErrorMessage"] = ex.Message.ToString();
return View("Error", TempData["ErrorMessage"]);
}
}
/* Since ASP.NET Identity and OWIN Cookie Authentication are claims-based system, the framework requires the app to generate a ClaimsIdentity for the user.
ClaimsIdentity has information about all the claims for the user, such as what roles the user belongs to. You can also add more claims for the user at this stage.
The highlighted code below in the SignInAsync method signs in the user by using the AuthenticationManager from OWIN and calling SignIn and passing in the ClaimsIdentity. */
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
static SearchResult CreateDirectoryEntry(string sAMAccountName, string[] requiredProperties)
{
DirectoryEntry ldapConnection = null;
try
{
// Create LDAP connection object
//ldapConnection = new DirectoryEntry("alpha.company.com");
ldapConnection = new DirectoryEntry("LDAP://OU=Company_Infrastructure, DC=company, DC=mydomain", "******", "******");
//ldapConnection.Path = connectionPath;
ldapConnection.AuthenticationType = AuthenticationTypes.Secure;
DirectorySearcher search = new DirectorySearcher(ldapConnection);
search.Filter = String.Format("(sAMAccountName={0})", sAMAccountName);
foreach (String property in requiredProperties)
search.PropertiesToLoad.Add(property);
SearchResult result = search.FindOne();
//SearchResultCollection searchResultCollection = search.FindAll();
if (result != null)
{
//foreach (String property in requiredProperties)
// foreach (Object myCollection in result.Properties[property])
// Console.WriteLine(String.Format("{0,-20} : {1}",
// property, myCollection.ToString()));
// return searchResultCollection;
return result;
}
else
{
return null;
//Console.WriteLine("User not found!");
}
//return ldapConnection;
}
catch (Exception e)
{
Console.WriteLine("Exception caught:\n\n" + e.ToString());
}
return null;
}
Note: In order to force signout in LDAP authentication, add FormsAuthentication.SignOut() line to the LogOff() method as shown below:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
AuthenticationManager.SignOut();
FormsAuthentication.SignOut(); //In order to force logout in LDAP authentication
return RedirectToAction("Login", "Account");
}
Step 2: Update your LoginViewModel (or whatever your Account model class is named) to contain only this LoginModel class:
public class LoginViewModel
{
[Required]
public string UserName { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
public bool RememberMe { get; set; }
}
On the other hand, add the custom properties i.e. Name, Surname, UserName, Department, etc. to the necessary model i.e. ApplicationUser, RegisterViewModel.
Step 3: Finally, update your Web.config file to include these elements:
<connectionStrings>
<!-- for LDAP -->
<add name="ADConnectionString" connectionString="LDAP://**.**.***:000/DC=abc,DC=xyz" />
</connectionStrings>
<system.web>
<!-- For LDAP -->
<httpCookies httpOnlyCookies="true" />
<authentication mode="Forms">
<forms name=".ADAuthCookie" loginUrl="~/Account/Login" timeout="30" slidingExpiration="true" protection="All" />
</authentication>
<membership defaultProvider="ADMembershipProvider">
<providers>
<clear />
<add name="ADMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider" connectionStringName="ADConnectionString" attributeMapUsername="sAMAccountName" connectionUsername="******" connectionPassword="******" />
</providers>
</membership>
...
</system.web>
Update: Here is the ApplicationUser class used in the example:
// Must be expressed in terms of our custom Role and other types:
public class ApplicationUser : IdentityUser<int, ApplicationUserLogin,
ApplicationUserRole, ApplicationUserClaim>, IUser<int>
{
public string Name { get; set; }
public string Surname { get; set; }
public async Task<ClaimsIdentity>
GenerateUserIdentityAsync(UserManager<ApplicationUser, int> manager)
{
var userIdentity = await manager
.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
return userIdentity;
}
}
I have used Active Directory Authetication at work.
I created application MVC with Windows Authetication and it was done. Application automatically displays AD login with domain.
Choose membership: [Authorize(Roles=#"DomainName\GroupName")]
Ye can see domain and groups in cmd: net user Username /domain
You haven't to use LDAP.
With LDAP see:
see here
i have a mixed system running. Users from a Database (external users) mixed with AD users (internal users) that can login to our system. to communicate with the AD i use a nuget package called LinqToLdap (https://github.com/madhatter22/LinqToLdap). this uses the LDAP protocol so it can be used for authenticating against Unix Ldap servers also.
this is the Authentication method
public bool AuthenticateUser(string userName, string password)
{
InitConfig();
using (var context = new DirectoryContext(_config))
{
var user = context.Query<LdapUserInfo>().FirstOrDefault(x => x.UserPrincipalName.Equals(userName));
var dn = user?.DistinguishedName;
if (string.IsNullOrWhiteSpace(dn))
return false;
using (var ldap = new LdapConnection(new LdapDirectoryIdentifier(_myConfig.Server)))
{
ldap.SessionOptions.ProtocolVersion = 3;
ldap.AuthType = AuthType.Basic;
ldap.Credential = _credentials;
ldap.Bind();
try
{
ldap.AuthType = AuthType.Basic;
ldap.Bind(new NetworkCredential(dn, password));
return true;
}
catch (DirectoryOperationException)
{ }
catch (LdapException)
{ }
}
return false;
}
}
private void InitConfig()
{
if (_config != null)
return;
_config = new LdapConfiguration();
_credentials = new NetworkCredential(_myConfig.Username, _myConfig.Password, _myConfig.Domain);
_config.AddMapping(new AutoClassMap<LdapGroupInfo>(), _myConfig.NamingContext, new[] { "*" });
_config.AddMapping(new AutoClassMap<LdapUserInfo>(), _myConfig.NamingContext, new[] { "*" });
_config.ConfigureFactory(_myConfig.Server).AuthenticateAs(_credentials).AuthenticateBy(AuthType.Basic);
}

Remove User from Roles in ASP.NET Identity 2.x

How can I remove User from Roles in ASP.NET Identity 2.x ?
about adding role to user there is no problem but when I want to remove a role from a user I cannot.It should be mentioned that there is no exception or error!
//POST: Admin/User/Edit/5
[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Prefix = "")]UserViewModel userViewModel, List<int> availableRoles)
{
if (ModelState.IsValid)
{
List<int> newListOfRolesIDs = availableRoles;
List<int> oldListOfRolesIDs = UserBLL.Instance.GetRolesIDs(userViewModel.Id);
List<int> deletedList;
List<int> addedList;
var haschanged = oldListOfRolesIDs.ChangeTracking(newListOfRolesIDs, out deletedList, out addedList);
using (new EFUnitOfWorkFactory().Create())
{
if (haschanged)
{
UserBLL.Instance.InsertRoles(addedList, userViewModel.Id);
UserBLL.Instance.DeleteRoles(deletedList, userViewModel.Id);
}
await UserBLL.Instance.UpdateAsync(userViewModel);
}
//ArticleBLL.Instance.UpdatePartial(articleViewModel, m => m.Title);
return RedirectToAction("Edit");
}
return View(userViewModel);
}
Delete Role method:
public void DeleteRoles(List<int> deleteList, int? userId)
{
if (userId != null)
{
User user = UserManager.FindByIdAsync(userId.Value).Result;
foreach (var i in deleteList)
{
user.Roles.Remove(new UserRole { RoleId = i, UserId = user.Id }); // What's the problem?!
}
}
}
Insert Role method:
public void InsertRoles(List<int> insertList, int? userId)
{
if (userId != null)
{
User user = UserManager.FindByIdAsync(userId.Value).Result;
foreach (var i in insertList)
{
user.Roles.Add(new UserRole { RoleId = i, UserId = user.Id });
}
}
}
What you are looking for is the RemoveFromRoleAsync method. An example would look similar to the following:
public async Task DeleteRolesAsync(List<string> deleteList, int? userId)
{
if (userId != null)
{
foreach (var roleName in deleteList)
{
IdentityResult deletionResult = await UserManager.RemoveFromRoleAsync(userId, roleName);
}
}
}
If you already have the ID of the user, there's no need to get the user again (only if you want to make sure that the user really exists; then you have to wrap your foreach with an if-statement). The deletion methods needs the name of the role, instead of the ID, to delete the user from the role. You can use the result of the operation (in my example stored in deletionResult) to make sure that the operation was successful. Remember that the name of the user manager (in my example UserManager) can vary depending on your implementation.
I had the same issue and what I ended up using was the
RemoveFromRolesAsync(string userId, params string[] roles) Method
from the UserManager.
Using the role names in an array works.
But has an issue that is if the user is not in one of the roles in the array the user will not be removed from any roles in the array.
All or nothing.
var usr = UserManager.FindById(usrV.ID.ToString());
string[] deleteList;
deleteList= new string[1];
deleteList[0] = "Engineer";
var rresult1 = UserManager.RemoveFromRolesAsync(usr.Id, deleteList);
Hope it helps
You might want to check out this blog post. The ASP.NET team has a sample that includes adding and removing roles from a user.
ASP.NET Identity 2.0: Customizing Users and Roles

MVC returning user roles to a view

I have used the following tutorial http://www.dotnetfunda.com/articles/show/2898/working-with-roles-in-aspnet-identity-for-mvc to add roles to my application. I have managed to add a role to a user that is stored within my database. However I am unable to list the roles that have been assigned to that user. I get the following error
Object reference not set to an instance of an object.
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
My controller looks like this
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult GetRoles(string UserName)
{
ApplicationDbContext context = new ApplicationDbContext();
if (!string.IsNullOrWhiteSpace(UserName))
{
ApplicationUser user = context.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
var account = new AccountController();
//this.UserManager.GetRoles(user.Id);
ViewBag.RolesForThisUser = account.UserManager.GetRoles(user.Id);
// 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");
}
and my view
<hr />
<h3>Get Roles for a User</h3>
#using (Html.BeginForm("GetRoles", "Account"))
{
#Html.AntiForgeryToken()
<p>
Username : #Html.TextBox("UserName")
<input type="submit" value="Get Roles for this User" />
</p>
}
#if (ViewBag.RolesForThisUser != null)
{
<div style="background-color:yellow;">
<h3>Roles for this user </h3>
<ol>
#foreach (string s in ViewBag.RolesForThisUser)
{
<li>#s</li>
}
</ol>
</div>
}
You can try this :
In your Controller class, insert :
using System.Threading.Tasks;
using Microsoft.AspNet.Identity.Owin;
next, the controller action :
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> GetRoles(string UserName)
{
using (ApplicationDbContext context = new ApplicationDbContext())
{
if (!string.IsNullOrWhiteSpace(UserName))
{
ApplicationUser user = context.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
//var account = new AccountController();
ApplicationUserManager UserManager = HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
//this.UserManager.GetRoles(user.Id);
ViewBag.RolesForThisUser = await UserManager.GetRolesAsync(user.Id);
// 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");
}
}
The view is OK. Please let me know if works for you.
I think that you selected a bad tutorial, if you read the comments at the end of the post all the people has the similar problem. By the way this article is confused. Multiple times, an instance of AccountController is created to access an instance of an object created on that controller (UserManager), forever tying this implementation directly to the existence of the default AccountController.
I leave few good tutorials
ASP.NET MVC 5 Identity: Extending and Modifying Roles
ASP.NET Identity 2.0: Customizing Users and Roles
Extending Identity Accounts and Implementing Role-Based Authentication in ASP.NET MVC 5
This sounded familiar, and I noticed this question the other day. I won't mark it as an exact duplicate, as i'm not entirely sure that it is.. but it's quite possible that you and this person were following the same tutorial.
MVC5 Account Controller null reference exception
EDIT: On further review, it looks like exactly the same issue.. so I went ahead and voted it as a duplicate.
Thanks all for the replies. I have managed to return the user roles by using the following.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult GetRoles(string UserName)
{
ApplicationDbContext context = new ApplicationDbContext();
if (!string.IsNullOrWhiteSpace(UserName))
{
ApplicationUser user = context.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
var ReturnRole = this.UserManager.GetRoles(user.Id);
// ViewBag.RolesForThisUser = account.UserManager.GetRoles(user.Id);
ViewBag.RolesForThisUser = ReturnRole;
// 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");
}
The above Post from Stefan Vlad will also work :) Thanks Stefan
All I need now is to delete a role from a user, here's hoping !

ASP.NET MVC Postbacks and HtmlHelper Controls is not reflecting Model Changes

I'm facing problems with a MVC5 Razor web application. I have an authentication page (cshtml) that has an Id and password helper controls:
#model NetInfinity.Middleware.VistaModelos.LoginVistaModelo
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<h1>#Login.Acceso</h1>
<p>
#Html.TextBoxFor(c => c.Id, new { #placeholder = #Login.Usuario, autofocus = "", autocomplete = "off", maxlength = "15", size = "15" })
</p>
<p class="p1">
#Html.PasswordFor(c => c.Clave, new { #placeholder = #Login.Contraseña, maxlength = "20", size = "20" })
#Html.ActionLink(".", "Cambiopwd", null, new { #class = "login-cambiarpwd", id = "Cambiopwd" })
</p>
<p class="login-recordarpwd">#Html.ActionLink(#Login.RecordarPwd, "Recordatoriopwd")</p>
<button type="button" class="login-submit" id="login-submit">#Login.LoginSubmit</button>
}
And the respective Model:
public class LoginVistaModelo
{
public string Id
{
get;
set;
}
[DataType(DataType.Password)]
public string Clave
{
get;
set;
}
public string MensajeError
{
get;
set;
}
}
And Controller Action that validates user is:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginVistaModelo vmUsuario)
{
if (ModelState.IsValid)
{
EntidadesBD backend;
var cache = MemoryCache.Default;
backend = (EntidadesBD)cache.Get("backend");
if (backend == null)
{
backend = new EntidadesBD();
var politica = new CacheItemPolicy { Priority = CacheItemPriority.NotRemovable };
cache.Set("backend", backend, politica);
}
Usuario usuario = vmUsuario.ValidaUsuario();
if (usuario == null)
{
vmUsuario.MensajeError = "error2";
vmUsuario.Id = vmUsuario.Clave = String.Empty; // <--- This not works
ModelState.Clear(); // <-- This not works
}
else
{
}
}
return View(vmUsuario);
}
When Login Action is triggered to validate user and password and error is thrown, I need to clear TextBoxFor value and PasswordFor value, and to achieve this I set model properties Id and Clave to string.empty in Controller, however when page (cshtml) is rendered again, controls keep old values ignoring model changes, not even if ModelState.Clear(). I've heard that HtmlHelpers controls (like .TextBoxFor() etc.) don't bind to model values on Postback, but rather get their value directly out of the POST buffer from ModelState. Please, ¿How can I do to update controls value when they are changed in Model properties?
Thanks
try making the value of model null before returning it to view,
like vmUsuario.id = null, vmUsuario.clave= null ; and thn return the empty model to view
A better approach for this type of problem would be to redirect the user, rather than returning the view. Otherwise you run into the problem that if they press F5 it reposts the data. So simply redirect the user, and use TempData to include your error message. In your Get method, check if TempData contains an error message and display it if it does.

Asp.net MVC Let user switch between roles

I'm developing a complex website with users having multiple roles. The users are also coupled on other items in the DB which, together with their roles, will define what they can see and do on the website.
Now, some users have more than 1 role, but the website can only handle 1 role at a time because of the complex structure.
the idea is that a user logs in and has a dropdown in the corner of the website where he can select one of his roles. if he has only 1 role there is no dropdown.
Now I store the last-selected role value in the DB with the user his other settings. When he returns, this way the role is still remembered.
The value of the dropdown should be accessible throughout the whole website.
I want to do 2 things:
Store the current role in a Session.
Override the IsInRole method or write a IsCurrentlyInRole method to check all access to the currently selected Role, and not all roles, as does the original IsInRole method
For the Storing in session part I thought it'd be good to do that in Global.asax
protected void Application_AuthenticateRequest(Object sender, EventArgs e) {
if (User != null && User.Identity.IsAuthenticated) {
//check for roles session.
if (Session["CurrentRole"] == null) {
NASDataContext _db = new NASDataContext();
var userparams = _db.aspnet_Users.First(q => q.LoweredUserName == User.Identity.Name).UserParam;
if (userparams.US_HuidigeRol.HasValue) {
var role = userparams.aspnet_Role;
if (User.IsInRole(role.LoweredRoleName)) {
//safe
Session["CurrentRole"] = role.LoweredRoleName;
} else {
userparams.US_HuidigeRol = null;
_db.SubmitChanges();
}
} else {
//no value
//check amount of roles
string[] roles = Roles.GetRolesForUser(userparams.aspnet_User.UserName);
if (roles.Length > 0) {
var role = _db.aspnet_Roles.First(q => q.LoweredRoleName == roles[0].ToLower());
userparams.US_HuidigeRol = role.RoleId;
Session["CurrentRole"] = role.LoweredRoleName;
}
}
}
}
}
but apparently this gives runtime errors. Session state is not available in this context.
How do I fix this, and is this
really the best place to put this
code?
How do I extend the user (IPrincipal?) with IsCurrentlyInRole without losing all other functionality
Maybe i'm doing this all wrong and there is a better way to do this?
Any help is greatly appreciated.
Yes, you can't access session in Application_AuthenticateRequest.
I've created my own CustomPrincipal. I'll show you an example of what I've done recently:
public class CustomPrincipal: IPrincipal
{
public CustomPrincipal(IIdentity identity, string[] roles, string ActiveRole)
{
this.Identity = identity;
this.Roles = roles;
this.Code = code;
}
public IIdentity Identity
{
get;
private set;
}
public string ActiveRole
{
get;
private set;
}
public string[] Roles
{
get;
private set;
}
public string ExtendedName { get; set; }
// you can add your IsCurrentlyInRole
public bool IsInRole(string role)
{
return (Array.BinarySearch(this.Roles, role) >= 0 ? true : false);
}
}
My Application_AuthenticateRequest reads the cookie if there's an authentication ticket (user has logged in):
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[My.Application.FORMS_COOKIE_NAME];
if ((authCookie != null) && (authCookie.Value != null))
{
Context.User = Cookie.GetPrincipal(authCookie);
}
}
public class Cookie
{
public static IPrincipal GetPrincipal(HttpCookie authCookie)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket != null)
{
string ActiveRole = "";
string[] Roles = { "" };
if ((authTicket.UserData != null) && (!String.IsNullOrEmpty(authTicket.UserData)))
{
// you have to parse the string and get the ActiveRole and Roles.
ActiveRole = authTicket.UserData.ToString();
Roles = authTicket.UserData.ToString();
}
var identity = new GenericIdentity(authTicket.Name, "FormAuthentication");
var principal = new CustomPrincipal(identity, Roles, ActiveRole );
principal.ExtendedName = ExtendedName;
return (principal);
}
return (null);
}
}
I've extended my cookie adding the UserData of the Authentication Ticket. I've put extra-info here:
This is the function which creates the cookie after the loging:
public static bool Create(string Username, bool Persistent, HttpContext currentContext, string ActiveRole , string[] Groups)
{
string userData = "";
// You can store your infos
userData = ActiveRole + "#" string.Join("|", Groups);
FormsAuthenticationTicket authTicket =
new FormsAuthenticationTicket(
1, // version
Username,
DateTime.Now, // creation
DateTime.Now.AddMinutes(My.Application.COOKIE_PERSISTENCE), // Expiration
Persistent, // Persistent
userData); // Additional informations
string encryptedTicket = System.Web.Security.FormsAuthentication.Encrypt(authTicket);
HttpCookie authCookie = new HttpCookie(My.Application.FORMS_COOKIE_NAME, encryptedTicket);
if (Persistent)
{
authCookie.Expires = authTicket.Expiration;
authCookie.Path = FormsAuthentication.FormsCookiePath;
}
currentContext.Response.Cookies.Add(authCookie);
return (true);
}
now you can access your infos everywhere in your app:
CustomPrincipal currentPrincipal = (CustomPrincipal)HttpContext.User;
so you can access your custom principal members: currentPrincipal.ActiveRole
When the user Changes it's role (active role) you can rewrite the cookie.
I've forgot to say that I store in the authTicket.UserData a JSON-serialized class, so it's easy to deserialize and parse.
You can find more infos here
If you truly want the user to only have 1 active role at a time (as implied by wanting to override IsInRole), maybe it would be easiest to store all of a user's "potential" roles in a separate place, but only actually allow them to be in 1 ASP.NET authentication role at a time. When they select a new role use the built-in methods to remove them from their current role and add them to the new one.

Resources