Custom Login for MVC3 application - asp.net-mvc-3

I want to build a website that displays user-specific information (e.g. user sees her own records when she logs in). I don't want to use MVC3 Internet Application with default Membership provider as I would like to experience building my own authentication. I found some links on the web but feel that I need more sources to read from.
My project is MVC3, Entity Framework with code first. Thank you,

Desciption
You can implement your own custom MemberShip Provider with your own logic.
More Information
ASP.NET MVC 2 Custom Membership Provider Tutorial
Codeplex: Custom Membership Providers
Using a Custom Membership Provider in an MVC Application
MSDN: Implementing a Membership Provider

I recently did a similar thing - we have an application written in classic ASP and I wanted to authenticate with .NET using the existing database tables at the same time (for anyone who's interested I first logged the user into the classic ASP side then posted their details to the .NET logon form causing it to do its own login process).
Pretty much everything I found suggested just writing my own MemberShip Provider (as suggested by (dknaack) and IPrincipal object to allow me to add additional fields I wanted (it was easier than extending the profile data).
It was actually very easy in the end; I didn't bother implementing all of the MembershipProvider functions because I didn't need them. Here I just use Entity framework to check if the username/password are valid but clearly you could use whatever you wanted.
Here's a dump of my code to get you started:
using System;
using System.Collections.Specialized;
using System.Linq;
using System.Security.Principal;
using System.Web.Security;
using My.Company.Project.Entities;
namespace My.Company.Project.Classes
{
public class ClassicMembershipProvider : MembershipProvider
{
public override void Initialize(string name, NameValueCollection config)
{
base.Initialize(name, config);
}
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
throw new NotImplementedException();
}
public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
{
throw new NotImplementedException();
}
public override string GetPassword(string username, string answer)
{
throw new NotImplementedException();
}
public override bool ChangePassword(string username, string oldPassword, string newPassword)
{
throw new NotImplementedException();
}
public override string ResetPassword(string username, string answer)
{
throw new NotImplementedException();
}
public override void UpdateUser(MembershipUser user)
{
throw new NotImplementedException();
}
public override bool ValidateUser(string username, string password)
{
using (var entities = new ProjectEntities())
{
return entities.Buprofile
.Where(p => p.profileid == username &&
p.profilepassword == password)
.Count() == 1;
}
}
public override bool UnlockUser(string userName)
{
throw new NotImplementedException();
}
public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
{
throw new NotImplementedException();
}
public override MembershipUser GetUser(string username, bool userIsOnline)
{
throw new NotImplementedException();
}
public override string GetUserNameByEmail(string email)
{
throw new NotImplementedException();
}
public override bool DeleteUser(string username, bool deleteAllRelatedData)
{
throw new NotImplementedException();
}
public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
{
throw new NotImplementedException();
}
public override int GetNumberOfUsersOnline()
{
throw new NotImplementedException();
}
public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
{
throw new NotImplementedException();
}
public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
{
throw new NotImplementedException();
}
public override bool EnablePasswordRetrieval
{
get { return false; }
}
public override bool EnablePasswordReset
{
get { return false; }
}
public override bool RequiresQuestionAndAnswer
{
get { return false; }
}
public override string ApplicationName
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
public override int MaxInvalidPasswordAttempts
{
get { throw new NotImplementedException(); }
}
public override int PasswordAttemptWindow
{
get { throw new NotImplementedException(); }
}
public override bool RequiresUniqueEmail
{
get { return true; }
}
public override MembershipPasswordFormat PasswordFormat
{
get { throw new NotImplementedException(); }
}
public override int MinRequiredPasswordLength
{
get { throw new NotImplementedException(); }
}
public override int MinRequiredNonAlphanumericCharacters
{
get { throw new NotImplementedException(); }
}
public override string PasswordStrengthRegularExpression
{
get { throw new NotImplementedException(); }
}
}
}
You can hook it up by adding/updating in your web.config:
<membership defaultProvider="ClassicMembershipProvider" userIsOnlineTimeWindow="15">
<providers>
<clear />
<add name="ClassicMembershipProvider" type="My.Company.Project.Classes.ClassicMembershipProvider" applicationName="MyApplication" />
</providers>
</membership>
I also created a custom IPrincipal (based on http://blog.codevelop.dk/post/2007/11/24/ASPNET-20-Forms-authentication-Keeping-it-customized-yet-simple.aspx) which allows you to store various bits of data about the user in a UserData property (as mentioned, it looked far easier this way than to extend the user profile).
You can access UserData of a user once logged in by casting User: ((MyPrincipal)User).UserData - I overode the User property in my page base class so I could just use User. in future though:
/// <summary>
/// Override User and return a MyPrincipal with its additional data
/// </summary>
public new MyPrincipal User
{
get { return HttpContext.Current.User is MyPrincipal ? (MyPrincipal) HttpContext.Current.User : null; }
}
In the end this was pretty pain-free, and far less hassle than writing my own from scratch yet I still have all the control I need.

Related

Custom validate attribute on query parameter web api

I have the following controller which takes guids in string format as query parameters.
I wanted to verify they were a valid guid before executing the method but I'm not sure how to fire my custom ValidationAttribute:
controller.cs
public async Task<Profile> GetProfile([ValidGuid] string id)
{
...
}
ValidGuidAttribute.cs
internal class ValidGuidAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
try
{
Guid.Parse(value.ToString());
return true;
}
catch (Exception e)
{
throw new InvalidIdException($"{value.ToString()} is an invalid id");
}
}
}
Any help pointing out how to fire my IsValid method would be appreciated.
You should use a route constraint https://learn.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2#route-constraints
[Route("getprofile/{id:guid}")]
public async Task<Profile> GetProfile(string id)
{
...
}

How to customize authentication to my own set of tables in asp.net web api 2?

In the default AccountController created I see
public AccountController()
: this(Startup.UserManagerFactory(), Startup.OAuthOptions.AccessTokenFormat)
{
}
In Startup.Auth.cs I see
UserManagerFactory = () =>
new UserManager<IdentityUser>(new UserStore<IdentityUser>());
Seems like the implementation of UserStore comes from Microsoft.AspNet.Identity.EntityFramework.
So, to customize the authentication do I have to implement my own version of UserStore like
class MYSTUFFUserStore<IdentityUser> : UserStore<IdentityUser>
{
}
and override the methods and then do this in Startup.Auth.cs
UserManagerFactory = () =>
new UserManager<IdentityUser>(new MYSTUFFUserStore<IdentityUser>());
I am looking for a correct way to customize the authentication.
Assuming your table is called AppUser, convert your own AppUser domain object to IUser(using Microsoft.AspNet.Identity) like this
using Microsoft.AspNet.Identity;
public class AppUser : IUser
{
//Existing database fields
public long AppUserId { get; set; }
public string AppUserName { get; set; }
public string AppPassword { get; set; }
public AppUser()
{
this.Id = Guid.NewGuid().ToString();
}
[Ignore]
public virtual string Id { get; set; }
[Ignore]
public string UserName
{
get
{
return AppUserName;
}
set
{
AppUserName = value;
}
}
}
Implement the UserStore object like this
using Microsoft.AspNet.Identity;
public class UserStoreService
: IUserStore<AppUser>, IUserPasswordStore<AppUser>
{
CompanyDbContext context = new CompanyDbContext();
public Task CreateAsync(AppUser user)
{
throw new NotImplementedException();
}
public Task DeleteAsync(AppUser user)
{
throw new NotImplementedException();
}
public Task<AppUser> FindByIdAsync(string userId)
{
throw new NotImplementedException();
}
public Task<AppUser> FindByNameAsync(string userName)
{
Task<AppUser> task = context.AppUsers.Where(
apu => apu.AppUserName == userName)
.FirstOrDefaultAsync();
return task;
}
public Task UpdateAsync(AppUser user)
{
throw new NotImplementedException();
}
public void Dispose()
{
context.Dispose();
}
public Task<string> GetPasswordHashAsync(AppUser user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
return Task.FromResult(user.AppPassword);
}
public Task<bool> HasPasswordAsync(AppUser user)
{
return Task.FromResult(user.AppPassword != null);
}
public Task SetPasswordHashAsync(AppUser user, string passwordHash)
{
throw new NotImplementedException();
}
}
If you have your own custom password hashing you will also need to implement IPasswordHasher. Below is an example where there is no hashing of the password(Oh no!)
using Microsoft.AspNet.Identity;
public class MyPasswordHasher : IPasswordHasher
{
public string HashPassword(string password)
{
return password;
}
public PasswordVerificationResult VerifyHashedPassword
(string hashedPassword, string providedPassword)
{
if (hashedPassword == HashPassword(providedPassword))
return PasswordVerificationResult.Success;
else
return PasswordVerificationResult.Failed;
}
}
In Startup.Auth.cs replace
UserManagerFactory = () =>
new UserManager<IdentityUser>(new UserStore<IdentityUser>());
with
UserManagerFactory = () =>
new UserManager<AppUser>(new UserStoreService()) { PasswordHasher = new MyPasswordHasher() };
In ApplicationOAuthProvider.cs, replace IdentityUser with AppUser
In AccountController.cs, replace IdentityUser with AppUser and delete all the external authentication methods like GetManageInfo and RegisterExternal etc.

Trying to call code in my controller but getting Null Reference error

Don't want to over-complicate the issue, but I think I need to post all the code that's hooked into this error.
Using MvcMailer and introduced a separate Send mechanism (for use with Orchard CMS' own EMail).
The MvcMailer Code:
1) AskUsMailer.cs:
public class AskUsMailer : MailerBase, IAskUsMailer
{
public AskUsMailer()
: base()
{
//MasterName = "_Layout";
}
public virtual MvcMailMessage EMailAskUs(AskUsViewModel model)
{
var mailMessage = new MvcMailMessage { Subject = "Ask Us" };
ViewData.Model = model;
this.PopulateBody(mailMessage, viewName: "EMailAskUs");
return mailMessage;
}
}
2) IAskUsMailer.cs:
public interface IAskUsMailer : IDependency
{
MvcMailMessage EMailAskUs(AskUsViewModel model);
}
3) AskUsController.cs: (GETTING NULL REFERENCE ERROR BELOW)
[Themed]
public ActionResult Submitted()
{
//This is the new call (see new code below):
//Note: Debugging steps through eMailMessagingService,
//then shows the null reference error when continuing to
//SendAskUs
eMailMessagingService.SendAskUs(askUsData);
//Below is normal MvcMailer call:
//AskUsMailer.EMailAskUs(askUsData).Send();
return View(askUsData);
}
Note: askUsData is defined in a separate block in the controller:
private AskUsViewModel askUsData;
protected override void OnActionExecuting(ActionExecutingContext
filterContext)
{
var serialized = Request.Form["askUsData"];
if (serialized != null) //Form was posted containing serialized data
{
askUsData = (AskUsViewModel)new MvcSerializer().
Deserialize(serialized, SerializationMode.Signed);
TryUpdateModel(askUsData);
}
else
askUsData = (AskUsViewModel)TempData["askUsData"] ??
new AskUsViewModel();
TempData.Keep();
}
protected override void OnResultExecuted(ResultExecutedContext
filterContext)
{
if (filterContext.Result is RedirectToRouteResult)
TempData["askUsData"] = askUsData;
}
I did not know how to get my EMailMessagingService.cs (see below) call into the controller, so in a separate block in the controller I did this:
private IEMailMessagingService eMailMessagingService;
public AskUsController(IEMailMessagingService eMailMessagingService)
{
this.eMailMessagingService = eMailMessagingService;
}
I think this is part of my problem.
Now, the new code trying to hook into Orchard's EMail:
1) EMailMessagingServices.cs:
public class EMailMessagingService : IMessageManager
{
private IAskUsMailer askUsMailer;
private IOrchardServices orchardServices;
public EMailMessagingService(IAskUsMailer askUsMailer,
IOrchardServices orchardServices)
{
this.orchardServices = orchardServices;
this.askUsMailer = askUsMailer;
this.Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public void SendAskUs(AskUsViewModel model)
{
var messageAskUs = this.askUsMailer.EMailAskUs(model);
messageAskUs.To.Add("email#email.com");
//Don't need the following (setting up e-mails to send a copy anyway)
//messageAskUs.Bcc.Add(AdminEmail);
//messageAskUs.Subject = "blabla";
Send(messageAskUs);
}
....
}
The EMailMessagingService.cs also contains the Send method:
private void Send(MailMessage messageAskUs)
{
var smtpSettings = orchardServices.WorkContext.
CurrentSite.As<SmtpSettingsPart>();
// can't process emails if the Smtp settings have not yet been set
if (smtpSettings == null || !smtpSettings.IsValid())
{
Logger.Error("The SMTP Settings have not been set up.");
return;
}
using (var smtpClient = new SmtpClient(smtpSettings.Host,
smtpSettings.Port))
{
smtpClient.UseDefaultCredentials =
!smtpSettings.RequireCredentials;
if (!smtpClient.UseDefaultCredentials &&
!String.IsNullOrWhiteSpace(smtpSettings.UserName))
{
smtpClient.Credentials = new NetworkCredential
(smtpSettings.UserName, smtpSettings.Password);
}
if (messageAskUs.To.Count == 0)
{
Logger.Error("Recipient is missing an email address");
return;
}
smtpClient.EnableSsl = smtpSettings.EnableSsl;
smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
messageAskUs.From = new MailAddress(smtpSettings.Address);
messageAskUs.IsBodyHtml = messageAskUs.Body != null &&
messageAskUs.Body.Contains("<") &&
messageAskUs.Body.Contains(">");
try
{
smtpClient.Send(messageAskUs);
Logger.Debug("Message sent to {0} with subject: {1}",
messageAskUs.To[0].Address, messageAskUs.Subject);
}
catch (Exception e)
{
Logger.Error(e, "An unexpected error while sending
a message to {0} with subject: {1}",
messageAskUs.To[0].Address, messageAskUs.Subject);
}
}
}
Now, in EMailMessagingService.cs I was getting an error that things weren't being implemented, so I auto-generated the following (don't know if this is part of my error):
public void Send(Orchard.ContentManagement.Records.ContentItemRecord recipient, string type, string service, System.Collections.Generic.Dictionary<string, string> properties = null)
{
throw new NotImplementedException();
}
public void Send(System.Collections.Generic.IEnumerable<Orchard.ContentManagement.Records.ContentItemRecord> recipients, string type, string service, System.Collections.Generic.Dictionary<string, string> properties = null)
{
throw new NotImplementedException();
}
public void Send(System.Collections.Generic.IEnumerable<string> recipientAddresses, string type, string service, System.Collections.Generic.Dictionary<string, string> properties = null)
{
throw new NotImplementedException();
}
public bool HasChannels()
{
throw new NotImplementedException();
}
public System.Collections.Generic.IEnumerable<string> GetAvailableChannelServices()
{
throw new NotImplementedException();
}
2) IEMailMessagingServices.cs
public interface IEMailMessagingService
{
MailMessage SendAskUs(AskUsViewModel model);
}
MvcMailer works fine without this addition (outside of Orchard), but I am trying to get everything working within Orchard.
I just cannot figure out what I am doing wrong. Any thoughts?
Sorry for excessive code.
IEmailMessaginService does not implement IDependency, so it can't be found by Orchard as a dependency. That's why it's null.

How to return Faults in JSON from AJAX Enabled WCF Service?

I have an AJAX-enabled WCF service (with enableWebScript in the behavior) that has a ValidationFault which I created.
Here's the service:
[ServiceContract]
public interface ICoreWCF
{
/// <summary>
/// Saves the Customer.
/// </summary>
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest)]
[FaultContract(typeof(ValidationFault))]
void Customer_Save(Customer customer);
}
Here's the fault:
[DataContract]
public class ValidationFault
{
[DataMember(Name = "success")]
public bool Success { get; set; }
[DataMember(Name = "msg")]
public string ValidationMessage { get; set; }
[DataMember(Name = "errors")]
public Dictionary<string, string> Errors { get; set; }
}
I would like to send this fault back to the client javascript.
The problem is that my custom fault's DataMembers are ignored and a general exception is returned.
How can I send the errors collection to the client?
I already tried writing my own IErrorHandler similar to this, such that it uses Exception Handling Application Block to convert an exception to a fault, and then the IErrorHandler serializes the resulting fault. But it appears that the JsonErrorHandler of the WebScriptingEnablingBehavior is not dealing well with the resulting Message object.
Thanks.
If you have implemented IErrorHandler and associated it to service using using custom behavior inherited from WebHttpBehavior as sighted by link then perhaps you should try adding default request/response format etc. For example,
private class CustomWebScriptBehavior : WebHttpBehavior
{
protected override void AddServerErrorHandlers(ServiceEndpoint endpoint,
System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
// clear current error handlers
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();
// add our error handler
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(
new ErrorHandler(true));
}
private WebMessageFormat _requestFormat;
private WebMessageFormat _responseFormat;
public CustomWebScriptBehavior()
{
_requestFormat = _responseFormat = WebMessageFormat.Json;
}
public override bool AutomaticFormatSelectionEnabled
{
get { return false; }
set { throw new NotSupportedException(); }
}
public override WebMessageBodyStyle DefaultBodyStyle
{
get { return WebMessageBodyStyle.WrappedRequest; }
set { throw new NotSupportedException(); }
}
public override WebMessageFormat DefaultOutgoingRequestFormat
{
get { return _requestFormat; }
set { _requestFormat = value; }
}
public override WebMessageFormat DefaultOutgoingResponseFormat
{
get { return _responseFormat; }
set { _responseFormat = value; }
}
}
This will eliminate the need to specify WebInvoke attribute for each method.
in webinvoke you can add RequestFormat=WebMessageFormat.Json, ResponseFormat=WebMessageFormat.Json
try it

Do not store password in .net membership

I use the built in membership system in a MVC 3 .net application. Later in the developement I will use an external web service for authentication. Hence I would only have to store the (unique) username in the membership system. All other user info can be retrieved through the webservice.
Therefore I wonder how to not store a password?
Don't worry about the storing of the password, just randomly generate and store a password when you create the user.
Have your account controller validate the password against the external webservice in the logon method, if its correct, then simply call FormsAuthentication.SetAuthCookie(userName, false /*persistantCookie*/), which will "login" the user :)
side note:
Have you though about how you will migrate the existing user's to the new external webservice if you only have their password hash/salts?
Not sure if I understand you correctly but I think the best solution to this is writing an custom membership provider. Basically this is just an class with an few functions overriden from the basic membership provider. Here you can implement your own logic for registering, logging in and logging out.
Found an example of an class I used an while back. Just write your own implementation. An other option is to work from your accountcontroller (like haz also mentioned) but I always tend not to implement too much logic into my controllers and let my services handle the business logic.
public class CustomMembershipProvider : MembershipProvider
{
private readonly IGenericService<User> _genericUserService;
public CustomMembershipProvider(IGenericService<User> genericUserService)
{
_genericUserService = genericUserService;
}
public CustomMembershipProvider() : this(new GenericService<User>())
{
}
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
throw new NotImplementedException();
}
public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
{
throw new NotImplementedException();
}
public override string GetPassword(string username, string answer)
{
throw new NotImplementedException();
}
public override bool ChangePassword(string username, string oldPassword, string newPassword)
{
throw new NotImplementedException();
}
public override string ResetPassword(string username, string answer)
{
throw new NotImplementedException();
}
public override void UpdateUser(MembershipUser user)
{
throw new NotImplementedException();
}
public override bool ValidateUser(string username, string password)
{
try
{
var encodedPassword = password.AsSha512();
var user = _genericUserService.First(u => u.Email == username && u.Password == string.Empty );
return user != null;
}
catch (Exception)
{
return false;
}
}
public override bool UnlockUser(string userName)
{
throw new NotImplementedException();
}
public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
{
throw new NotImplementedException();
}
public override MembershipUser GetUser(string username, bool userIsOnline)
{
var user = _genericUserService.First(x => x.Email.Equals(username));
var a = new MembershipUser("", user.Firstname, user.Id, user.Email, "", "", true, user.Active,
user.RegisteredOn, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now);
return a;
}
public override string GetUserNameByEmail(string email)
{
throw new NotImplementedException();
}
public override bool DeleteUser(string username, bool deleteAllRelatedData)
{
throw new NotImplementedException();
}
public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
{
throw new NotImplementedException();
}
public override int GetNumberOfUsersOnline()
{
throw new NotImplementedException();
}
public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
{
throw new NotImplementedException();
}
public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
{
throw new NotImplementedException();
}
public override bool EnablePasswordRetrieval
{
get { throw new NotImplementedException(); }
}
public override bool EnablePasswordReset
{
get { throw new NotImplementedException(); }
}
public override bool RequiresQuestionAndAnswer
{
get { throw new NotImplementedException(); }
}
public override string ApplicationName
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
public override int MaxInvalidPasswordAttempts
{
get { throw new NotImplementedException(); }
}
public override int PasswordAttemptWindow
{
get { throw new NotImplementedException(); }
}
public override bool RequiresUniqueEmail
{
get { throw new NotImplementedException(); }
}
public override MembershipPasswordFormat PasswordFormat
{
get { throw new NotImplementedException(); }
}
public override int MinRequiredPasswordLength
{
get { throw new NotImplementedException(); }
}
public override int MinRequiredNonAlphanumericCharacters
{
get { throw new NotImplementedException(); }
}
public override string PasswordStrengthRegularExpression
{
get { throw new NotImplementedException(); }
}
}

Resources