I am trying to create a custom authentication scheme in ASP.NET MVC using form authentication. The idea that I might have different areas on the site that will be managed - approver are and general user area, and these will use different login pages, and so forth. So this is what I want to happen.
User access restricted page (right now I have it protected with a customer AuthorizeAttribute)
User is redirected to a specific login page (not the one from Web.config).
User credentials are verified (via custom databse scheme) and user logs in.
Would really appreciate any help with this!!!
This is what I what I have so far, and it doesn't work:
public class AdministratorAccountController : Controller
{
public ActionResult Login()
{
return View("Login");
}
[HttpPost]
public ActionResult Login(AdministratorAccountModels.LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
if (model.UserName == "admin" && model.Password == "pass") // This will be pulled from DB etc
{
var ticket = new FormsAuthenticationTicket(1, // version
model.UserName, // user name
DateTime.Now, // create time
DateTime.Now.AddSeconds(30), // expire time
false, // persistent
""); // user data
var strEncryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, strEncryptedTicket);
Response.Cookies.Add(cookie);
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
// If we got this far, something failed, redisplay form
return View(model);
}
[AdministratorAuthorize]
public ActionResult MainMenu()
{
return View();
}
public class AdministratorAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authenCookie = httpContext.Request.Cookies.Get(FormsAuthentication.FormsCookieName);
if (authenCookie == null) return false;
var ticket = FormsAuthentication.Decrypt(authenCookie.Value);
var id = new FormsIdentity(ticket);
var astrRoles = ticket.UserData.Split(new[] { ',' });
var principal = new GenericPrincipal(id, astrRoles);
httpContext.User = principal;
return true;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
var model = new AdministratorAccountModels.LoginModel();
var viewData = new ViewDataDictionary(model);
filterContext.Result = new ViewResult { ViewName = "Login", ViewData = viewData };
}
}
}
I used a combination of code suggested by minus4 and my own code above to create this simplified scenario that might help someone else. I added some comments about things that confused me at first.
public class AdministratorAccountController : Controller
{
public ActionResult Login()
{
return View("Login");
}
[HttpPost]
public ActionResult Login(AdministratorAccountModels.LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
// Here you would call a service to process your authentication
if (model.UserName == "admin" && model.Password == "pass")
{
// * !!! *
// Creating a FromsAuthenticationTicket is what
// will set RequestContext.HttpContext.Request.IsAuthenticated to True
// in the AdminAuthorize attribute code below
// * !!! *
var ticket = new FormsAuthenticationTicket(1, // version
model.UserName, // user name
DateTime.Now, // create time
DateTime.Now.AddSeconds(30), // expire time
false, // persistent
""); // user data, such as roles
var strEncryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, strEncryptedTicket);
Response.Cookies.Add(cookie);
// Redirect back to the page you were trying to access
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
// If we got this far, something failed, redisplay form
return View(model);
}
[AdminAuthorize]
public ActionResult MainMenu()
{
return View();
}
public class AdminAuthorize : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.RequestContext.HttpContext.Request.IsAuthenticated)
{
// Redirect to the needed login page
// This can be pulled from config file or anything else
filterContext.HttpContext.Response.Redirect("/AdministratorAccount/Login?ReturnUrl="
+ HttpUtility.UrlEncode(filterContext.HttpContext.Request.RawUrl));
}
base.OnActionExecuting(filterContext);
}
}
}
okay here you go The Code
in there you have ActionFilters folder ( AuthAccess.cs)
Plugins Folder (security.cs (encrypt/decrypt cookie), SessionHandler.cs (all matters of login))
Controllers folder (BaseController.cs, and exampleController (show you how to use)
and the loginTable SQL file.
i use mysql so you may need to amend, also i use subsonic so my model would come from there
and would be in the empty models folder.
really simple to use will leave it up for a while for you, enjoy
nope cookie model is here sorry:
using System;
namespace TestApp.Models
{
public class CookieModel
{
public string CurrentGuid { get; set; }
public DateTime LoginTime { get; set; }
public Int32 UserLevel { get; set; }
public Int32 LoginID { get; set; }
public bool isValidLogin { get; set; }
public string realUserName { get; set; }
public string emailAddress { get; set; }
}
}
Isn't this what roles are for?
Have a look at asp.net mvc authorization using roles or have a look at roles in general
i tackled this one before i have a class i use for login
routines are login, read cookie, check cookie and they have a model that contains
name, email, id, userlevel
then you just have your own custom actionFilter
eg [CustomAuth(MinAllowedLevel=10)]
i use a baseclass for all my controllers so i can have an easier link to
all my session content and can then get info like so
var model = pictures.all().where(x => x.userid == users.ReadCookie.userID)
i will bob up the code tommorow if you want for you when im back on UK daytime
say 10 hrs i will let you have the class for all the session stuff and the
custom action filter that you can use, then all you need is a logins table with a userlevel field, best with levels of 10,20,30,40 incase you need a level between 1 and 2
Related
I am fairly new to coding with asp.net so there might be an obvious answere to my question but I haven't found one yet.
So currently I am developing a site for project management and I want the users to get notified when an event happens, eg. they were added to a new project, a project has been updated etc.
For that I have expanded the IdentityUser Model with a new property List
public class CojectUser : IdentityUser
{
public List<Notification> Notifications { get; set; }
}
public class Notification
{
public int NotificationID { get; set; }
public string Message { get; set; }
public bool Seen { get; set; }
}
When an event happens I add them to the user's notification list and update the user via the userManager.
public class EventBroker<T> : IEventBroker<T>
{
private readonly UserManager<CojectUser> userManager;
public EventBroker(UserManager<CojectUser> userMgr, IUserValidator<CojectUser> userValid)
{
userManager = userMgr;
}
public async Task NotifyAsync(Message<T> message, List<UserRole> recipients)
{
foreach (var user in recipients)
{
var cojectUser = await userManager.FindByNameAsync(user.Name);
if (cojectUser != null)
{
if (cojectUser.Notifications == null)
{
cojectUser.Notifications = new List<Notification>();
}
cojectUser.Notifications.Add(new Notification
{
Message = message.Information,
Seen = false
});
IdentityResult result = await userManager.UpdateAsync(cojectUser);
if (!result.Succeeded)
{
throw new UserUpdateFailException();
}
}
}
}
}
}
I am able to save the custom data to the database, but I am unable to load it again from database.
When I want to display the user's notifications userManager retrieves an user object with null as notification list. Even though the data is stored in database.
public async Task<IActionResult> Index()
{
CojectUser user = await userManager.GetUserAsync(User);
if(user.Notifications == null)
{
user.Notifications = new List<Notification>();
}
return View(user);
}
Data in database:
Can anybody tell me what I am doing wrong?
UserManager don't eager load properties by default.
You should use DatabaseContext directly.
var user = _context.Users.Include(c => c.Notifications).Where(u => u.Id == user.Id).ToList();
I want to create a dynamic role in ASP.NET MVC 5. I do not want to create hardcode roles in the authorization attribute .I want to create roles later.it's a test for my recruitment.Do you have sample code or video In this case?
Just in ASP.NET MVC 5.
Thanks in advance for your help
You mean you need dynamic authorization.
In order to do this.
1.You need to add two more tables(Except identity tables).
AppContent (Columns:{Id, Resource, Function,Description})
RoleRights (Columns:{Id, RoleName,AppContentId).
2.Create CustomAuthorizeAttribute
[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class CustomAuthorize : AuthorizeAttribute
{
//Custom named parameters for annotation
public string Source { get; set; }//Controller Name
public string Function { get; set; }//Action Name
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//Is user logged in?
if (httpContext.User.Identity.IsAuthenticated)
{
if ((!string.IsNullOrEmpty(ResourceKey)) && (!string.IsNullOrEmpty(OperationKey)))
{
//There are many ways to store and validate RoleRights
//1.You can store in Database and validate from Database.
//2.You can store in user claim at the time of login and validate from UserClaims.
//3.You can store in session validate from session
//Below I am using database approach.
var loggedInUserRoles = ((ClaimsIdentity) httpContext.User.Identity).Claims
.Where(c => c.Type == ClaimTypes.Role)
.Select(c => c.Value);
//logic to check loggedInUserRoles has rights or not from RoleRights table
return db.RoleRights.Any( x=> x.AppContent.Source == Source && x.AppContent.Function == Function && loggedInUserRoles.Contains( x.AppContent.RoleName));
}
}
//Returns true or false, meaning allow or deny. False will call HandleUnauthorizedRequest above
return base.AuthorizeCore(httpContext);
}
//Called when access is denied
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
//User isn't logged in
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
base.HandleUnauthorizedRequest(filterContext);
return;
}
//User is logged in but has no access
else
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new { controller = "Account", action = "NotAuthorized" })
);
}
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
// Check for authorization
if (string.IsNullOrEmpty(this.Source) && string.IsNullOrEmpty(this.Function))
{
this.Source = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
this.Function = filterContext.ActionDescriptor.ActionName;
}
base.OnAuthorization(filterContext);
}
}
3. Assign CustomAuthorizeAttribute to the Controller Action
[CustomAuthorize(Source= "Branch", Function = "Index")]
public ActionResult Index()
{
return View(model);
}
[CustomAuthorize(Source = "Branch", Function = "Details")]
public ActionResult Details(long? id)
{
return View(branch);
}
[CustomAuthorize(Source = "Branch", Function = "Create")]
public ActionResult Create()
{
return View();
}
4.Setup all of your application content like Source(Controller) and Function(Action) in AppContent table.
5.Assign AppContents to a role for allowing to role to access this content.
6.Assign User to Role.
7.Run the application and test.
I trying to write a kind of pseudo singleton implementation. I want it to work similar to how HttpContext does work, where I can get an instance to the context doing something as simple as:
var ctx = HttpContext.Current;
So my implementation goes something like this:
public class AppUser
{
public string Username { get; set; }
public string[] Roles { get; set; }
public AppUser()
{
var appuser = HttpContext.Session["AppUser"] as AppUser;
if(appuser == null)
throw new Exception("User session has expired");
Username = appuser.Username;
Roles = appuser.Roles;
}
}
public class WebAppContext
{
const string ContextKey = "WebAppContext";
WebAppContext() { } //empty constructor
public static WebAppContext Current
{
get
{
var ctx = HttpContext.Current.Items[ContextKey] as WebAppContext;
if(ctx == null)
{
try
{
ctx = new WebAppContext() { User = new AppUser() };
}
catch
{
//Redirect for login
}
HttpContext.Current.Items.Add(ContextKey, ctx);
}
return ctx;
}
}
public AppUser User { get; set; }
}
And I try to consume this object as follows:
var appuser = WebAppContext.Current.User;
Now does the above line guarantee I get the user associated with the correct request context; not some other user which is associated with another concurrent http request being processed?
Apart from the fact that I can't understand why would you need to barely copy the user information from the Session container to the Items container, the answer to your question should be - yes, if the Session data is correct then the same data will be available from your static property.
I wrote a blog entry on that once
http://netpl.blogspot.com/2010/12/container-based-pseudosingletons-in.html
So what I'm doing might seem simple, but I don't know exactly how to do it.
I have already registered and logged in with an account (I'm using the default membership system used in ASP.NET MVC 4) and so I want to do add my UserId to some data I'm inserting to the database.
This is the model of the data I'm inserting:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Reroute.Models
{
public class Request
{
public int RequestId { get; set; }
// I want to add UserId based on my current session
public int UserId { get; set; }
public string OrderNumber { get; set; }
public string TrackingNumber { get; set; }
public string CurrentAddress { get; set; }
public string NewAddress { get; set; }
public string Comment { get; set; }
}
}
And the ActionResult (here's where I supposed I have to make the changes):
[HttpPost]
public ActionResult Create(Request collection)
{
try
{
_db.Requests.Add(collection);
_db.SaveChanges();
//return RedirectToAction("Index");
return Content("Done! Added to DB");
}
catch
{
return View();
}
}
Thanks
use this it gets u the userid ...
Membership.GetUser().ProviderUserKey
You can save the UserId of the authenticated user in Session after logging in:
Session["UserId"] = userId;
or since you are using FormsAuthentication you can either use the UserData property as shown here or do a nice-that-will-do-trick:
public SignInUser(string name, string id) {
// store the userid
FormsAuthentication.SetAuthCookie(name + '|' + id, false);
}
then retrieve the Name and UserId like this:
public int CurrentUserId
{
get
{
var context = HttpContext.Current;
if (context == null) return 0;
return context.Request.IsAuthenticated
? Convert.ToInt32(context.User.Identity.Name.Split('|')[1])
: 0;
}
}
public string CurrentUserName
{
get
{
var context = HttpContext.Current;
if (context == null) return string.Empty;
return context.Request.IsAuthenticated
? context.User.Identity.Name.Split('|')[0]
: string.Empty;
}
}
You can have those method and properties in a class so you have them in one place, I actually do it that way. Now, you can call it in your controller like so:
[HttpPost]
public ActionResult Create(Request collection)
{
try
{
collection.UserId = _authProvider.CurrentUserId;
// if you want to use session, I prefer the FormsAuthentication approach
// you need to do additional check that the Session has not expired (not null)
collection.UserId = Session["UserId"];
_db.Requests.Add(collection);
_db.SaveChanges();
//return RedirectToAction("Index");
return Content("Done! Added to DB");
}
catch
{
return View();
}
}
_authProvider is an instance of the class that has the code I gave above.
This should work.
var loggedInUserName=Thread.CurrentPrincipal.Identity.Name;
var user=Membership.GetUser(loggedInUserName);
var key = user.ProviderUserKey;
T
Assuming your Create also has a GET which is loaded up and used as the model for Create.cshtml, you would just need to set it explicitly in that ActionResult
public ActionResult Create()
{
Result model = new Result();
model.UserId = myUserId;
}
Then in your Create.cshtml you could have a hidden field for it:
#Html.HiddenFor(m => m.UserId)
I would still check in the POST to make sure the user doing the saving is allowed to be saving and hasn't spoofed your hidden field value to somebody completely different.
I want to create a Unit test for the following controller but it got fail in the Membership class:
public class AccountController:BaseController
{
public IFormsAuthenticationService FormsService { get; set; }
public IMembershipService MembershipService { get; set; }
protected override void Initialize(RequestContext requestContext)
{
if(FormsService == null) { FormsService = new FormsAuthenticationService(); }
if(MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
public ActionResult LogOn()
{
return View("LogOn");
}
[HttpPost]
public ActionResult LogOnFromUser(LappLogonModel model, string returnUrl)
{
if(ModelState.IsValid)
{
string UserName = Membership.GetUserNameByEmail(model.Email);
if(MembershipService.ValidateUser(model.Email, model.Password))
{
FormsService.SignIn(UserName, true);
var service = new AuthenticateServicePack();
service.Authenticate(model.Email, model.Password);
return RedirectToAction("Home");
}
}
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View("LogOn", model);
}
}
Unit test code:
[TestClass]
public class AccountControllerTest
{
[TestMethod]
public void LogOnPostTest()
{
var mockRequest = MockRepository.GenerateMock();
var target = new AccountController_Accessor();
target.Initialize(mockRequest);
var model = new LogonModel() { UserName = "test", Password = "1234" };
string returnUrl = string.Empty;
ActionResult expected = null;
ActionResult actual = target.LogOn(model, returnUrl);
if (actual == null)
Assert.Fail("should have redirected");
}
}
When I googled, I got the following code but I don't know how to pass the membership to the accountcontroller
var httpContext = MockRepository.GenerateMock();
var httpRequest = MockRepository.GenerateMock();
httpContext.Stub(x => x.Request).Return(httpRequest);
httpRequest.Stub(x => x.HttpMethod).Return("POST");
//create a mock MembershipProvider & set expectation
var membershipProvider = MockRepository.GenerateMock();
membershipProvider.Expect(x => x.ValidateUser(username, password)).Return(false);
//create a stub IFormsAuthentication
var formsAuth = MockRepository.GenerateStub();
/*But what to do here???{...............
........................................
........................................}*/
controller.LogOnFromUser(model, returnUrl);
Please help me to get this code working.
It appears as though you are using concrete instances of the IMembershipServive and IFormsAuthenticationService because you are using the Accessor to initialize them. When you use concrete classes you are not really testing this class in isolation, which explains the problems you are seeing.
What you really want to do is test the logic of the controller, not the functionalities of the other services.
Fortunately, it's an easy fix because the MembershipService and FormsService are public members of the controller and can be replaced with mock implementations.
// moq syntax:
var membershipMock = new Mock<IMembershipService>();
var formsMock = new Mock<IFormsAuthenticationService>();
target.FormsService = formsMock.Object;
target.MembershipService = membershipService.Object;
Now you can test several scenarios for your controller:
What happens when the MembershipService doesn't find the user?
The password is invalid?
The user and password is is valid?
Note that your AuthenticationServicePack is also going to cause problems if it has additional services or dependencies. You might want to consider moving that to a property of the controller or if it needs to be a single instance per authentication, consider using a factory or other service to encapsuate this logic.