Showing posts with label MVC 5. Show all posts
Showing posts with label MVC 5. Show all posts

Tuesday, September 15, 2015

reCAPTCHA 2.0

The folks at Google have released a new version of their CAPTCHA product named reCAPTCHA.  They refer to the new version as the No CAPTCHA reCAPTCHA.  It's fairly easy to implement even in an ASP.NET MVC web application as I'll show below.

First, go here and sign up.  You input the domains at which your site will be hosted.  LocalHost always "just works" so don't worry about development.  However, if you're testing at a different domain than production, be sure to put both in there.  You'll be given two keys.  The "site" key and the "secret" key.

Implementing the CAPTCHA is a two part process.

First Part - Display the CAPTCHA control for the user to "solve"

Add this to the top of your view:

<script src="https://www.google.com/recaptcha/api.js" async defer></script>

Then add this where you want the CAPTCHA box to appear:

<div class="g-recaptcha" data-sitekey="SITE-KEY-GOES-HERE" ></div>

That's all.

Second Part - Verify that the CAPTCHA was solved correctly

This is the more tricky of the two parts.

Once the user submits the form, reCAPTCHA is going to insert an additional form field named g-recaptcha-response into the form.  This will look like gobbly-gook.  This response along with the secret key needs to be sent to a Google web service.  The web service will return a JSON object that contains a success attribute.  If the success attribute is true, the user solved the CAPTCHA and you can safely assume that he or she is not a bot.

First, in your controller, add a data contract that you can deserialize the JSON response into:

<System.Runtime.Serialization.DataContract>
Private Class GoogleResults
    <System.Runtime.Serialization.DataMember(Name:="success")>
    Public Success As Boolean
End Class

Next, in your post action method within the controller use the following code:

Dim RecaptchaResponse As String = Request("g-recaptcha-response")

Dim MyClient As System.Net.WebClient = New System.Net.WebClient

Dim Reply As String = _
            MyClient.DownloadString(String.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", "SECRET-KEY-GOES-HERE", RecaptchaResponse))

Dim MySerializer As New System.Runtime.Serialization.Json.DataContractJsonSerializer(GetType(GoogleResults))

Dim MyStream = New System.IO.MemoryStream(Encoding.Unicode.GetBytes(Reply))

Dim Results As GoogleResults = CType(MySerializer.ReadObject(MyStream), GoogleResults)

If Not Results.Success Then
    ModelState.AddModelError("", "You must verify that you are not a robot.")
End If

If Not ModelState.IsValid Then
    Return View(model)
End If


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.

Tuesday, May 13, 2014

MVC Exception Handling

This is a followup post to my original post about simple .NET exception handling.

The ASP.NET MVC Framework contains an attribute named HandleErrorAttribute.  You can decorate a controller or an action simply by typing <HandleError> above the controller or action definition.  If an unhandled exception occurs anywhere in the scope decorated with HandleErrorAttribute, MVC will display a view named "Error" to the user automatically.  Typically, this view is generated for you when you create a new MVC application and is placed in the Shared folder.  The HandleErrorAttribute will only work if you have customErrors turned on in your web.config.  HandleErrorAttribute works great but it doesn't log.  In order to add this functionality to the attribute you need to create a new class that inherits from the HandleErrorAttribute like this:

    Public Class HandleErrorAndLogAttribute
        Inherits HandleErrorAttribute

        Public Overrides Sub OnException(filterContext As ExceptionContext)

            If Not filterContext.ExceptionHandled Then

                Dim MyException As Exception = filterContext.Exception
                
                ...

                'Logging code goes here

                ...
 
                MyBase.OnException(filterContext)

            End If

            'filterContext.Result = New ViewResult With {.ViewName = "Error"}

        End Sub

    End Class

Now, if you want to use your new attribute, you would just type <HandleErrorAndLog>.  You can globally apply this attribute to everything in your application by editing the FilterConfig.vb file like this:

Public Module FilterConfig
    Public Sub RegisterGlobalFilters(ByVal filters As GlobalFilterCollection)
        'filters.Add(New HandleErrorAttribute())
        filters.Add(New HandleErrorAndLogAttribute())
    End Sub
End Module

Apparently, there are certain exceptions that this attribute does not handle such as HTTP exceptions that occur outside of the MVC context.  To ensure these errors are handled, you should also log errors in the MvcApplication_Error event of the Global.asax.vb file.  Be sure to set the defaultRedirect attribute of the customErrors node in the web.config file to the Error View.  You will need to create an action method that will display the Error view when it is redirected in this way..

Tuesday, March 25, 2014

Add a Bootstrap jQuery widget to an MVC view.

Bootstrap is an awesome open source UI framework that uses both CSS and jQuery to provide a great user experience.  Besides straight up CSS, they offer some "widgets" that offer some functionality that jQuery UI does not provide.  The process for getting a widget on your view is almost exactly the same as using jQuery UI.

You have to render both the jQuery and Bootstrap bundles in your layout view.  If you don't have a Bootstrap bundle, you just need Bootstrap.js.  Also ensure Bootstrap.css is in your CSS bundle.

This markup will render a button that will produce the popover widget when clicked.

<button type="button" id="Example" class="btn btn-default" data-container="body" data-toggle="popover" data-placement="top" data-content="Phone:  555-5555">
   Popover on top
</button>

Then, add a jQuery file with the following:

$(document).ready(function () {
    $("#Example").popover();
});

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.