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.
Related
I am writing a Razor Pages app for a University project, which I am required to test. I couldn't find many sources and examples online on testing Razor Pages and I'm trying to follow the examples on this link : https://learn.microsoft.com/en-us/aspnet/core/testing/razor-pages-testing?view=aspnetcore-2.1
My first problem is unit testing:
This is the test method I wrote, it's supposed to check that a value that is filled in the OnGet method on my model is receiving the correct value:
[Fact]
public void OnGet_ViewStores()
{
// Arrange
var httpContext = new DefaultHttpContext();
var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
var pageContext = new PageContext(actionContext)
{
ViewData = viewData
};
var storesModel = new StoresModel()
{
PageContext = pageContext,
Url = new UrlHelper(actionContext)
};
#region snippet2
// Act
storesModel.OnGet();
#endregion
#region snippet3
// Assert
var actualStores = wsep1.Services.ViewAllStores(1);
Assert.Equal(storesModel.StoreDetails, actualStores);
#endregion
}
And this is the model which is being checked:
public class StoresModel : PageModel
{
public List<string> StoreDetails { get; set; }
public string Message { get; set; }
public int clientId;
public void OnGet()
{
clientId = (int)HttpContext.Session.GetInt32("clientId");
Message = "Your clientId id is " + clientId;
StoreDetails = wsep1.Services.ViewAllStores(clientId);
}
}
The problem is that the test throws an exception because I am using an HttpContext.Session which is not configures properly in the test. In my real project it is configured beforehand in Startup.cs in this method:
public void ConfigureServices(IServiceCollection services)
{
services.AddWebSocketManager();
services.AddMvc();
services.AddDistributedMemoryCache();
services.AddTransient<ShoppingHandler>();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(1000);
options.Cookie.HttpOnly = true;
});
}
But I can't seem to find a way to configure this in my test.
My second problem is with integration testing:
I am trying to run a very basic test with the Test Server, this is my test class:
public class IndexPageTest : IClassFixture<TestFixture<Client.Startup>>
{
private readonly HttpClient _client;
public IndexPageTest(TestFixture<Client.Startup> fixture)
{
_client = fixture.Client;
}
#region snippet1
[Fact]
public async Task Request_ReturnsSuccess()
{
// Act
var response = await _client.GetAsync("/");
// Assert
response.EnsureSuccessStatusCode();
}
#endregion
}
I hardly changed the TextFixture class that was included in the demo project in the link I gave at the beginning of the post, all I did was add my services to the configuration method (as I said before, I'm using a Session object and also WebSocketManager in my app).
_client.GetAsync("/") returns a status of "500 - internal server error" and I have no idea why and how to configure these tests to work.
Any ideas would be appreciated, Thanks.
I've done this before with MVC5 using User.Identity.GetUserId() but that doesn't seem to work here.
The User.Identity doesn't have the GetUserId() method.
I am using Microsoft.AspNet.Identity.
Update in ASP.NET Core Version >= 2.0
In the Controller:
public class YourControllerNameController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
public YourControllerNameController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task<IActionResult> YourMethodName()
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier) // will give the user's userId
var userName = User.FindFirstValue(ClaimTypes.Name) // will give the user's userName
// For ASP.NET Core <= 3.1
ApplicationUser applicationUser = await _userManager.GetUserAsync(User);
string userEmail = applicationUser?.Email; // will give the user's Email
// For ASP.NET Core >= 5.0
var userEmail = User.FindFirstValue(ClaimTypes.Email) // will give the user's Email
}
}
In some other class:
public class OtherClass
{
private readonly IHttpContextAccessor _httpContextAccessor;
public OtherClass(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void YourMethodName()
{
var userId = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
}
}
Then you should register IHttpContextAccessor in the Startup class as follows:
public void ConfigureServices(IServiceCollection services)
{
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Or you can also register as follows
services.AddHttpContextAccessor();
}
For more readability write extension methods as follows:
public static class ClaimsPrincipalExtensions
{
public static T GetLoggedInUserId<T>(this ClaimsPrincipal principal)
{
if (principal == null)
throw new ArgumentNullException(nameof(principal));
var loggedInUserId = principal.FindFirstValue(ClaimTypes.NameIdentifier);
if (typeof(T) == typeof(string))
{
return (T)Convert.ChangeType(loggedInUserId, typeof(T));
}
else if (typeof(T) == typeof(int) || typeof(T) == typeof(long))
{
return loggedInUserId != null ? (T)Convert.ChangeType(loggedInUserId, typeof(T)) : (T)Convert.ChangeType(0, typeof(T));
}
else
{
throw new Exception("Invalid type provided");
}
}
public static string GetLoggedInUserName(this ClaimsPrincipal principal)
{
if (principal == null)
throw new ArgumentNullException(nameof(principal));
return principal.FindFirstValue(ClaimTypes.Name);
}
public static string GetLoggedInUserEmail(this ClaimsPrincipal principal)
{
if (principal == null)
throw new ArgumentNullException(nameof(principal));
return principal.FindFirstValue(ClaimTypes.Email);
}
}
Then use as follows:
public class YourControllerNameController : Controller
{
public IActionResult YourMethodName()
{
var userId = User.GetLoggedInUserId<string>(); // Specify the type of your UserId;
var userName = User.GetLoggedInUserName();
var userEmail = User.GetLoggedInUserEmail();
}
}
public class OtherClass
{
private readonly IHttpContextAccessor _httpContextAccessor;
public OtherClass(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void YourMethodName()
{
var userId = _httpContextAccessor.HttpContext.User.GetLoggedInUserId<string>(); // Specify the type of your UserId;
}
}
Until ASP.NET Core 1.0 RC1 :
It's User.GetUserId() from System.Security.Claims namespace.
Since ASP.NET Core 1.0 RC2 :
You now have to use UserManager.
You can create a method to get the current user :
private Task<ApplicationUser> GetCurrentUserAsync() => _userManager.GetUserAsync(HttpContext.User);
And get user information with the object :
var user = await GetCurrentUserAsync();
var userId = user?.Id;
string mail = user?.Email;
Note :
You can do it without using a method writing single lines like this string mail = (await _userManager.GetUserAsync(HttpContext.User))?.Email, but it doesn't respect the single responsibility principle. It's better to isolate the way you get the user because if someday you decide to change your user management system, like use another solution than Identity, it will get painful since you have to review your entire code.
you can get it in your controller:
using System.Security.Claims;
var userId = this.User.FindFirstValue(ClaimTypes.NameIdentifier);
or write an extension method like before .Core v1.0
using System;
using System.Security.Claims;
namespace Shared.Web.MvcExtensions
{
public static class ClaimsPrincipalExtensions
{
public static string GetUserId(this ClaimsPrincipal principal)
{
if (principal == null)
throw new ArgumentNullException(nameof(principal));
return principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
}
}
}
and get wherever user ClaimsPrincipal is available :
using Microsoft.AspNetCore.Mvc;
using Shared.Web.MvcExtensions;
namespace Web.Site.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return Content(this.User.GetUserId());
}
}
}
I included using System.Security.Claims and I could access the GetUserId() extension method
NB: I had the using Microsoft.AspNet.Identity already but couldn't get the extension method. So I guess both of them have to be used in conjunction with one another
using Microsoft.AspNet.Identity;
using System.Security.Claims;
EDIT:
This answer is now outdated. Look at Soren's or Adrien's answer for a dated way of achieving this in CORE 1.0
For .NET Core 2.0 Only The following is required to fetch the UserID of the logged-in User in a Controller class:
var userId = this.User.FindFirstValue(ClaimTypes.NameIdentifier);
or
var userId = HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
e.g.
contact.OwnerID = this.User.FindFirstValue(ClaimTypes.NameIdentifier);
As stated somewhere in this post, the GetUserId() method has been moved to the UserManager.
private readonly UserManager<ApplicationUser> _userManager;
public YourController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public IActionResult MyAction()
{
var userId = _userManager.GetUserId(HttpContext.User);
var model = GetSomeModelByUserId(userId);
return View(model);
}
If you started an empty project you might need to add the UserManger to your services in startup.cs. Otherwise this should already be the case.
you have to import Microsoft.AspNetCore.Identity & System.Security.Claims
// to get current user ID
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
// to get current user info
var user = await _userManager.FindByIdAsync(userId);
For ASP.NET Core 2.0, Entity Framework Core 2.0, AspNetCore.Identity 2.0 api (https://github.com/kkagill/ContosoUniversity-Backend):
The Id was changed to User.Identity.Name
[Authorize, HttpGet("Profile")]
public async Task<IActionResult> GetProfile()
{
var user = await _userManager.FindByIdAsync(User.Identity.Name);
return Json(new
{
IsAuthenticated = User.Identity.IsAuthenticated,
Id = User.Identity.Name,
Name = $"{user.FirstName} {user.LastName}",
Type = User.Identity.AuthenticationType,
});
}
Response:
In .net core 3.1 (and other more recent versions), you can use:
private readonly UserManager<IdentityUser> _userManager;
public ExampleController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
Then:
string userId = _userManager.GetUserId(User);
Or async:
var user = await _userManager.GetUserAsync(User);
var userId = user.Id;
At this point, I'm trying to figure out why you'd use one over the other. I know the general benefits of async, but see both of these used frequently. Please post some comments if anyone knows.
For ASP.NET 5.0, I have an extension method as follow:
using System;
using System.ComponentModel;
using System.Security.Claims;
namespace YOUR_PROJECT.Presentation.WebUI.Extensions
{
public static class ClaimsPrincipalExtensions
{
public static TId GetId<TId>(this ClaimsPrincipal principal)
{
if (principal == null || principal.Identity == null ||
!principal.Identity.IsAuthenticated)
{
throw new ArgumentNullException(nameof(principal));
}
var loggedInUserId = principal.FindFirstValue(ClaimTypes.NameIdentifier);
if (typeof(TId) == typeof(string) ||
typeof(TId) == typeof(int) ||
typeof(TId) == typeof(long) ||
typeof(TId) == typeof(Guid))
{
var converter = TypeDescriptor.GetConverter(typeof(TId));
return (TId)converter.ConvertFromInvariantString(loggedInUserId);
}
throw new InvalidOperationException("The user id type is invalid.");
}
public static Guid GetId(this ClaimsPrincipal principal)
{
return principal.GetId<Guid>();
}
}
}
So you can use it like:
using Microsoft.AspNetCore.Mvc;
using YOUR_PROJECT.Presentation.WebUI.Extensions;
namespace YOUR_PROJECT.Presentation.WebUI.Controllers
{
public class YourController :Controller
{
public IActionResult YourMethod()
{
// If it's Guid
var userId = User.GetId();
// Or
// var userId = User.GetId<int>();
return View();
}
}
}
in the APiController
User.FindFirst(ClaimTypes.NameIdentifier).Value
Something like this you will get the claims
Although Adrien's answer is correct, you can do this all in single line. No need for extra function or mess.
It works I checked it in ASP.NET Core 1.0
var user = await _userManager.GetUserAsync(HttpContext.User);
then you can get other properties of the variable like user.Email. I hope this helps someone.
For getting current user id in razor views, we can inject UserManager in the view like this:
#inject Microsoft.AspNetCore.Identity.UserManager<ApplicationUser> _userManager
#{ string userId = _userManager.GetUserId(User); }
I hope you find it useful.
User.Identity.GetUserId();
does not exist in asp.net identity core 2.0. in this regard, i have managed in different way. i have created a common class for use whole application, because of getting user information.
create a common class PCommon & interface IPCommon
adding reference using System.Security.Claims
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Common.Web.Helper
{
public class PCommon: IPCommon
{
private readonly IHttpContextAccessor _context;
public PayraCommon(IHttpContextAccessor context)
{
_context = context;
}
public int GetUserId()
{
return Convert.ToInt16(_context.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier));
}
public string GetUserName()
{
return _context.HttpContext.User.Identity.Name;
}
}
public interface IPCommon
{
int GetUserId();
string GetUserName();
}
}
Here the implementation of common class
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Logging;
using Pay.DataManager.Concreate;
using Pay.DataManager.Helper;
using Pay.DataManager.Models;
using Pay.Web.Helper;
using Pay.Web.Models.GeneralViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Pay.Controllers
{
[Authorize]
public class BankController : Controller
{
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger _logger;
private readonly IPCommon _iPCommon;
public BankController(IUnitOfWork unitOfWork, IPCommon IPCommon, ILogger logger = null)
{
_unitOfWork = unitOfWork;
_iPCommon = IPCommon;
if (logger != null) { _logger = logger; }
}
public ActionResult Create()
{
BankViewModel _bank = new BankViewModel();
CountryLoad(_bank);
return View();
}
[HttpPost, ActionName("Create")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Insert(BankViewModel bankVM)
{
if (!ModelState.IsValid)
{
CountryLoad(bankVM);
//TempData["show-message"] = Notification.Show(CommonMessage.RequiredFieldError("bank"), "Warning", type: ToastType.Warning);
return View(bankVM);
}
try
{
bankVM.EntryBy = _iPCommon.GetUserId();
var userName = _iPCommon.GetUserName()();
//_unitOfWork.BankRepo.Add(ModelAdapter.ModelMap(new Bank(), bankVM));
//_unitOfWork.Save();
// TempData["show-message"] = Notification.Show(CommonMessage.SaveMessage(), "Success", type: ToastType.Success);
}
catch (Exception ex)
{
// TempData["show-message"] = Notification.Show(CommonMessage.SaveErrorMessage("bank"), "Error", type: ToastType.Error);
}
return RedirectToAction(nameof(Index));
}
}
}
get userId and name in insert action
_iPCommon.GetUserId();
Thanks,
Maksud
TLDR:
In the Controler add:
using System.Security.Claims;
and then you can use:
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
endof TLDR;
Just an easy way in dot net 6 to test how to get the userID and test it in the default Blazor WebAssembly Core Hosted:
I added a String in WeatherForecast class named userId
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string userId { get; set; } = "nope";
}
Then in the WeatherForecastController
I add
using System.Security.Claims;
In the GET method I set WeatherForecast.userId to User.FindFirstValue(ClaimTypes.NameIdentifier):
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)],
userId = User.FindFirstValue(ClaimTypes.NameIdentifier)
})
.ToArray();
}
And finally in the FetchData.razor I modify the table to:
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
<th>User Id</th>
</tr>
</thead>
<tbody>
#foreach (var forecast in forecasts)
{
<tr>
<td>#forecast.Date.ToShortDateString()</td>
<td>#forecast.TemperatureC</td>
<td>#forecast.TemperatureF</td>
<td>#forecast.Summary</td>
<td>#forecast.userId</td>
</tr>
}
</tbody>
</table>
And then finally I get:
I hope it helps because in net core 6 sometimes it's quite difficult to find the answers
If you are using JWT tokens this code works:
User.FindFirstValue("sub");
use can use
string userid = User.FindFirst("id").Value;
for some reason NameIdentifier now retrieve the username (.net core 2.2)
Make sure that you have enable windows authentication. If you have anonymous authentication enabled you may be getting a null string.
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth?view=aspnetcore-3.1&tabs=visual-studio
I know there are many answers posted already, but maybe it will help someone as it did for me.
I mixed two solutions into one, and I am able to get the logged-in User and its Data.
I was using DotNet 5.
Following code, help to get the logged-in User.
var user = await _userManager.FindByNameAsync(HttpContext.User.Identity.Name);
I used the following package for _userManager
using Microsoft.AspNetCore.Identity;
And for HttpContext, I inherit my Controller from ControllerBase, and for ControllerBase Class I was using the following package
using Microsoft.AspNetCore.Mvc;
As an administrator working on other people's profile and you need to get the Id of the profile you are working on, you can use a ViewBag to capture the Id e.g ViewBag.UserId = userId; while userId is the string Parameter of the method you are working on.
[HttpGet]
public async Task<IActionResult> ManageUserRoles(string userId)
{
ViewBag.UserId = userId;
var user = await userManager.FindByIdAsync(userId);
if (user == null)
{
ViewBag.ErrorMessage = $"User with Id = {userId} cannot be found";
return View("NotFound");
}
var model = new List<UserRolesViewModel>();
foreach (var role in roleManager.Roles)
{
var userRolesViewModel = new UserRolesViewModel
{
RoleId = role.Id,
RoleName = role.Name
};
if (await userManager.IsInRoleAsync(user, role.Name))
{
userRolesViewModel.IsSelected = true;
}
else
{
userRolesViewModel.IsSelected = false;
}
model.Add(userRolesViewModel);
}
return View(model);
}
If you want this in ASP.NET MVC Controller, use
using Microsoft.AspNet.Identity;
User.Identity.GetUserId();
You need to add using statement because GetUserId() won't be there without it.
There are many samples online using OWIN/Katana to find users in a database based on ausername/password combination and generate a claims principal, such as...
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
// generate claims here...
That's fine if you're creating a new application and want Entity Framework to do the dirty work. But, I have an eight year old monolithic web site that has just been updated to use claims-based authentication. Our database hit is done manually via DAL/SQL and then the ClaimsIdentity is generated from there.
Some people are suggesting that OWIN is easier to use than our manual approach, but I'd like some input from those that use it.
Is it possible to alter how the UserManager factory finds users based on their credentials? Or, is there another approach that I've missed? All the samples I can find online seem to use a boilerplate approach of letting Entity Framework create the database and manage the searches.
ASP.NET Identity is a little bit overly complex, I would say.
In August 2014 they've announced the new version 2.1 and things have changed again.
First of all let's get rid of EntityFramework:
Uninstall-Package Microsoft.AspNet.Identity.EntityFramework
Now we implement our own definition of User implementing the interface IUser (Microsoft.AspNet.Identity):
public class User: IUser<int>
{
public User()
{
this.Roles = new List<string>();
this.Claims = new List<UserClaim>();
}
public User(string userName)
: this()
{
this.UserName = userName;
}
public User(int id, string userName): this()
{
this.Id = Id;
this.UserName = userName;
}
public int Id { get; set; }
public string UserName { get; set; }
public string PasswordHash { get; set; }
public bool LockoutEnabled { get; set; }
public DateTime? LockoutEndDateUtc { get; set; }
public bool TwoFactorEnabled { get; set; }
public IList<string> Roles { get; private set; }
public IList<UserClaim> Claims { get; private set; }
}
As you can see I have defined the type of my Id (int).
Then you have to define your custom UserManager inheriting from Microsoft.AspNet.Identity.UserManager specifying the your user type and the key type.
public class UserManager : UserManager<User, int>
{
public UserManager(IUserStore<User, int> store): base(store)
{
this.UserLockoutEnabledByDefault = false;
// this.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(10);
// this.MaxFailedAccessAttemptsBeforeLockout = 10;
this.UserValidator = new UserValidator<User, int>(this)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = false
};
// Configure validation logic for passwords
this.PasswordValidator = new PasswordValidator
{
RequiredLength = 4,
RequireNonLetterOrDigit = false,
RequireDigit = false,
RequireLowercase = false,
RequireUppercase = false,
};
}
}
I've implemented my validation rules here but you can keep it outside if you prefer.
UserManager needs a UserStore (IUserStore).
You will define your DB logic here. There are a few interfaces to implement. Not all of them are mandatory though.
public class UserStore :
IUserStore<User, int>,
IUserPasswordStore<User, int>,
IUserLockoutStore<User, int>,
IUserTwoFactorStore<User, int>,
IUserRoleStore<User, int>,
IUserClaimStore<User, int>
{
// You can inject connection string or db session
public UserStore()
{
}
}
I haven't included all the methods for each interface. Once you have done that you'll be able to write your new user:
public System.Threading.Tasks.Task CreateAsync(User user)
{
}
fetch it by Id:
public System.Threading.Tasks.Task<User> FindByIdAsync(int userId)
{
}
and so on.
Then you'll need to define your SignInManager inheriting from Microsoft.AspNet.Identity.Owin.SignInManager.
public class SignInManager: SignInManager<User, int>
{
public SignInManager(UserManager userManager, IAuthenticationManager authenticationManager): base(userManager, authenticationManager)
{
}
public override Task SignInAsync(User user, bool isPersistent, bool rememberBrowser)
{
return base.SignInAsync(user, isPersistent, rememberBrowser);
}
}
I've only implemented SignInAsync: it will generates a ClaimsIdentity.
That's pretty much it.
Now in your Startup class you have to tell Owin how to create the UserManager and the SignInManager.
app.CreatePerOwinContext<Custom.Identity.UserManager>(() => new Custom.Identity.UserManager(new Custom.Identity.UserStore()));
// app.CreatePerOwinContext<Custom.Identity.RoleManager>(() => new Custom.Identity.RoleManager(new Custom.Identity.RoleStore()));
app.CreatePerOwinContext<Custom.Identity.SignInService>((options, context) => new Custom.Identity.SignInService(context.GetUserManager<Custom.Identity.UserManager>(), context.Authentication));
I haven't used the factories you will find in the default template cause I wanted to keep things as simple as possible.
And enable your application to use the cookie:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<Custom.Identity.UserManager, Custom.Identity.User, int>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) =>
{
var userIdentity = manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
return (userIdentity);
},
getUserIdCallback: (id) => (Int32.Parse(id.GetUserId()))
)}
});
Now in your account controller - or the controller responsible for the login - you will have to get the UserManager and the SignInManager:
public Custom.Identity.SignInManager SignInManager
{
get
{
return HttpContext.GetOwinContext().Get<Custom.Identity.SignInManager>();
}
}
public Custom.Identity.UserManager UserManager
{
get
{
return HttpContext.GetOwinContext().GetUserManager<Custom.Identity.UserManager>();
}
}
You will use the SignInManager for the login:
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
and the UserManager to create the user, add roles and claims:
if (ModelState.IsValid)
{
var user = new Custom.Identity.User() { UserName = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// await UserManager.AddToRoleAsync(user.Id, "Administrators");
// await UserManager.AddClaimAsync(user.Id, new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Country, "England"));
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
It seems complicate ... and it is ... kind of.
If you want to read more about it there's a good explanation here and here.
If you want to run some code and see how it works, I've put together some code which works with Biggy (as I didn't want to waste to much time defining tables and stuff like that).
If you have the chance to download my code from the github repo, you'll notice that I have created a secondary project (Custom.Identity) where I've kept all my ASP.NET Identity stuff.
The only nuget packages you will need there are:
Microsoft.AspNet.Identity.Core
Microsoft.AspNet.Identity.Owin
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
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