Showing posts with label ASP.NET Identity. Show all posts
Showing posts with label ASP.NET Identity. Show all posts

Tuesday, September 15, 2015

Implementing Forgot Password and Email Confirmation in ASP.NET Identity

This is one of those tasks that is not very difficult to actually accomplish but exceedingly difficult to find good instructions on how to do it.  Forgot Password is simply a page where the user can enter his or her email address if he has forgotten his/her password.  He/she then receives an email containing a link where the password can be reset.  Email Confirmation means that when a user first registers, he/she cannot log in until the email is confirmed.  How is the email confirmed?  The person is sent an email containing a link to click that will mark the address as confirmed.  This all sounds complicated, but ASP.NET Identity builds all of this for you when you create an ASP.NET web application with Individual Accounts.  However, the code in the AccountController is commented out initially.  Because email is a central part of these operations, you must first configure your email.

To configure your email, just open up the IdentityConfig.vb file.  Find the EmailService class and add code to the SendAsync method.  The code below illustrates some code that sends an email using a traditional SMTP server:

Public Class EmailService
    Implements IIdentityMessageService

    Public Function SendAsync(message As IdentityMessage) As Task Implements IIdentityMessageService.SendAsync
        ' Plug in your email service here to send an email.

        Dim SmtpServer As String

        If System.Configuration.ConfigurationManager.AppSettings("SmtpServer") Is Nothing Then
            Throw New Exception("The SmtpServer key is not present in the web.config file.")
        Else
            SmtpServer = System.Configuration.ConfigurationManager.AppSettings("SmtpServer")
        End If

        Dim FromAddress As String

        If System.Configuration.ConfigurationManager.AppSettings("ConfirmationEmailFrom") Is Nothing Then
            Throw New Exception("The ConfirmationEmailFrom key is not present in the web.config file.")
        Else
            FromAddress = System.Configuration.ConfigurationManager.AppSettings("ConfirmationEmailFrom")
        End If

        Dim MyClient As New System.Net.Mail.SmtpClient(SmtpServer)

        Dim MyMessage As New System.Net.Mail.MailMessage

        MyMessage.From = New Net.Mail.MailAddress(FromAddress)

        MyMessage.Subject = message.Subject

        MyMessage.Body = message.Body
        MyMessage.IsBodyHtml = True

        MyMessage.To.Add(message.Destination)

        MyClient.Send(MyMessage)

        Return Task.FromResult(0)

    End Function

End Class

Once this is configured, go back to your AccountController and uncomment out the appropriate lines.

Friday, August 1, 2014

Setting up a secure WCF web service from "scratch" with ASP.NET Identity

For this example, I'm using Visual Studio 2013 with Update 2 installed.  Update 2 will ensure that the templates use the latest and greatest versions of all technologies involved.

Add a project using the WCF Service Application template.  You could also use the Asp.Net Web Application template, but I think you'll just end up with a bunch of extra references you don't need.  Now take a look at the web.config file.  You'll notice that using this template automatically adds the protocolMapping element which allows the service to be accessed using https.  If you did not use this template, you would have had to add this for https support.  You'll also notice that there are no endpoints or bindings configured.  That's because WCF has default bindings and IIS can determine the endpoint by examining the .svc file.

I delete the sample service created for you.

Next, I find it convenient to host the web services I'm developing on my local IIS.  Click on My Project, Web tab, and type http://{name of your computer}.{domain}.{com}/{something meaningful}.  It is important to use the fully qualified name of your computer because the certificate you will soon use is named the same and WCF won't like it if they are not the same.  Click Create Virtual Directory.  Open up IIS Manager, and change the app pool of the site to one with an associated identity of your (hopefully powerful) account.  This becomes important when you connect to a database for authentication.

Add your web service by using the WCF Service template.  Build the solution, and right click the service.  Choose View in Browser to make sure all is well so far.

Now it's time to enable SSL (https).  Read here to do this.  At this point, you'll want to change the Project URL to include https on the Web tab when you go to My Project.  I think it's a good idea to go ahead and setup a binding configuration and an endpoint.  As I said earlier, WCF can figure all this out because of default values.  However, Visual Studio's Add Service Reference automatic proxy generator can get confused when you are setting up a client.  You can either use the Microsoft Service Configuration Editor (Right click web.config and choose Edit WCF Configuration) tool or edit the web.config by hand.  If you use the tool, just click the Service folder, and choose Create a New Service.  This will walk you through a wizard to create the service node.  After that there is a link to create the binding configuration.  You end up with this inside the <system.serviceModel> element:

    <bindings>
      <basicHttpsBinding>
        <binding name="BasicHttpsBindingConfig" />
      </basicHttpsBinding>
    </bindings>
    <services>
      <service name="AssemblyName.YourService">
        <endpoint address="https://ComputerName.domain.whatever/IISSite/YourService.svc"
          binding="basicHttpsBinding" bindingConfiguration="BasicHttpsBindingConfig"
          contract="AssemblyName.IYourService" />
      </service>
    </services>

You also need to set the multipleSiteBindingEnabled to False when you are using https:

<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="false" />

At this point, I would create a little test app in a completely different solution and make sure you can call your new web service.  A Windows Console app will work great.  Just Add, Service Reference and specify the endpoint of the service.  By adding the reference, Visual Studio will add the following to your app.config file:

        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpsBinding_IYourService">
                    <security mode="Transport" />
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="https://ComputerName.domain.whatever/IISSite/YourService.svc"
                binding="basicHttpBinding" bindingConfiguration="BasicHttpsBinding_IYourService"
                contract="BenefitsServiceReference.IYourService" name="BasicHttpsBinding_IYourService" />
        </client>

There are a couple of things to note here.  Why does the client configuration set up for basicHttpBinding when the server configuration is set up for basicHttpsBinding?  Essentially basicHttpBinding and basicHttpsBinding are exactly the same.  The only difference is that basicHttpsBinding includes the <security mode="Transport" /> by default.

Now that the web service is up and running with SSL we want to configure it to require credentials in order to access it.  You can use the ASP.NET Membership, but this has been superceded by ASP.NET Identity and WCF doesn't directly interface with ASP.NET Identity yet.  So, we will create a custom user name and password validator which we will eventually use to authenticate with ASP.NET Identity.  First, we need to create the validator class itself.  Add references to the following:
  • System.IdentityModel
  • System.IdentityModel.Selectors
Add this class to your project:

Imports System.IdentityModel.Selectors
Imports System.ServiceModel

Public Class CustomUserNameValidator
    Inherits UserNamePasswordValidator

    Public Overrides Sub Validate(userName As String, password As String)

        If Not (userName = "Steve" AndAlso password = "topsecret") Then

            Throw New FaultException("Unknown Username or Incorrect Password")

        End If

    End Sub

End Class

It's pretty obvious what is happening here.  If your User Name and Password is not right, an exception gets thrown back to the client.  Now we have to configure the web service to use this.  First of all we need to edit our binding configuration.  Remember that by default basicHttpsBinding uses Transport as the Security mode.  We need to change this to TransportWithMessageCredential.  We are protecting (SSL) the service at the transport (IIS) level and authenticating at the message (web service) level.  Here is the binding configuration after we do that:

    <bindings>
      <basicHttpsBinding>
        <binding name="BasicHttpsBindingConfig">
          <security mode="TransportWithMessageCredential" />
        </binding>
      </basicHttpsBinding>
    </bindings>

You also need to add a serviceCredentials behavior to your existing behavior element:

    <behaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
          <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="Custom"
              customUserNamePasswordValidatorType="AssemblyName.CustomUserNameValidator, AssemblyName" />
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>

As with most configuration changes, here you're definitely better off using the Service Configuration Editor tool.  This is where we specify our custom class for validating the user name and password.  Note the specific way that the class is specified along with the assembly that contains the class.

The only configuration change in your client console app is changing the security mode to "TransportWithMessageCredential".  When you run your console, you will notice that you'll get an exception if you fail to provide credentials.  To specify the client's credentials, do the following:

        YourServiceProxy.ClientCredentials.UserName.UserName = "Steve"
        YourServiceProxy.ClientCredentials.UserName.Password = "topsecret"

If the client provides accurate credentials, then the service will return results; otherwise, an exception will be thrown.

With this infrastructure in place, you can change the code in the CustomUserNameValidator class to connect to any database you want to authenticate.  I mentioned earlier my desire to access ASP.NET Identity to authenticate users for the web service.  In a separate solution, I have set up an ASP.NET MVC Web Application and configured it with ASP.NET Identity authentication.  I am going to use this web application to administer user accounts for my web service.  I simply add this class to my MVC project:

Imports Microsoft.AspNet.Identity
Imports Microsoft.AspNet.Identity.EntityFramework

Public Module SecurityManager

    Public Function IsAuthenticated(userName As String, password As String) As Boolean

        Dim MyUserStore As UserStore(Of IdentityUser) = New UserStore(Of IdentityUser)
        Dim MyUserManager As UserManager(Of IdentityUser) = New UserManager(Of IdentityUser)(MyUserStore)

        Dim MyUser As IdentityUser = MyUserManager.Find(userName, password)

        If MyUser Is Nothing Then
            Return False
        Else
            Return True
        End If

    End Function

End Module

Now, if I can call this function from my CustomUserNameValidator in my WCF project, I will be authenticating using ASP.NET Identity.  First add a reference to the dll produced by building the MVC project.  Then, simply add this code to your CustomUserNameValidator:

        If Not SecurityManager.IsAuthenticated(userName, password) Then
            Throw New FaultException("Unknown Username or Incorrect Password")
        End If

Of course, it's not quite that easy.  You need to do two more things:
  1. Use NuGet to install the same version of Entity Framework that your MVC project uses.  This will setup your web.config with some special Entity Framework stuff that you will need.
  2. Copy the connectionStrings section out of the MVC web.config file and put it into the WCF web.config file.  This connection string contains the database where the identity data is.
To get the name of the user who is accessing your service use this line:

System.ServiceModel.ServiceSecurityContext.Current.PrimaryIdentity.Name

So there we go.  We now have a secure web service.  Only accessible via https and secured by credentials stored in an ASP.NET Identity database.

Friday, March 21, 2014

ASP.NET Identity - Get a user id

After a user logs in, how do you get the user id, which is stored as a GUID?  You don't have to go through the UserManager class which would require a trip to the database.  You can use the following code but you have to include the Imports statement or it won't work.

Imports Microsoft.AspNet.Identity

...

User.Identity.GetUserId()

Thursday, March 20, 2014

ASP.NET Identity - Using Roles

Roles allow you to restrict parts of your application to specific roles.  These roles are stored in the AspNetRoles table that is created by ASP.NET Identity.  You can create roles programatically or just insert them directly into the table using SQL.  If you're using SQL, you can just use an INSERT statement with a GUID and a name.  Programmatically, you would do the following:

Dim MyRoleStore As New RoleStore(Of IdentityRole)
Dim MyRoleManager As New RoleManager(Of IdentityRole)(MyRoleStore)

MyRoleManager.Create(New IdentityRole("Support Staff"))

IdentityRole is the Entity Framework object that ultimately represents (mapped to) a row in the AspNetRoles table.

Once you have some roles defined, you can associate a role to a user like this:

Dim Result As IdentityResult = _
     Await UserManager.AddToRoleAsync(MyUser.Id, "Support Staff")

Finally in order to restrict parts of your application, you simple add an Authorize filter to the ActionMethod you want restricted like this:

<Authorize(Roles:="Support Staff")>
Function MyActionMethod() As ActionResult

    ...

    Return View()

End Function

Finally, if the user is not authorized, they will be redirected to a specific page, usually the login page.  ASP.NET Identity is not forms authentication so this redirect is not in the web.config.  In an MVC app, it is in the Startup.Auth class ConfigureAuth method:

app.UseCookieAuthentication(New CookieAuthenticationOptions() With {
.AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
.LoginPath = New PathString("/Account/Login")})

Tuesday, March 18, 2014

ASP.NET Identity Basics

ASP.NET Identity is a system for handling authentication and authorization.  It was released with .NET 4.5.1 that shipped with Visual Studio 2013.  It replaces ASP.NET Membership.

Like Membership, it is a (sort of) simple way to implement forms authentication in a .NET application.  Identity differs from Membership in that it can be unit tested, allows external providers (such as Facebook and Google), and it works for all .NET technologies.

It is surprisingly easy to configure a new MVC web application for forms authentication using Identity.  Simply add an ASP.NET Web Application project and choose MVC with Individual User Accounts.  Remember to set the Framework version of the new project to 4.5.1 or you will not have these options.

The resulting project will contain:
  1. An AccountController class that will handle all authentication and user profile functionality.
  2. A web.config with a connection string pointing at a yet-to-be-created LocalDB database.
  3. A set of Account views that handles logging in, registrations, and user profile management.
  4. An AccountViewModels file that contain view models the Account views use.
  5. An IdentityModel file that contain classes that derive from the built in Identity classes.
  6. A Startup.Auth file that contains code to configure authentication on start up.
All of this is nicely integrated with the default views and controllers that the ASP.NET template has included for a standard MVC application.  You can run it and start registering and logging in immediately with no additional configuration.

It is important to understand the users and passwords are stored in the LocalDB database referred to in the web.config file.  This database is created by ASP.NET when you register the first user.  When you want to go to production, you can just script this thing out and move it to a production SQL Server.

If you look at the AccountController code, you'll notice that a Microsoft.AspNet.Identity.UserManager class is called for much of the authentication functionality.  The UserManager class almost exclusively offers asynchronous methods.  Therefore, you have to follow the rules of calling asynchronous methods when using this class.