Adding OpenID authentication to your ASP.NET MVC 4 application - StrathWeb

Strath

August 1st, 2012

Adding OpenID authentication to your ASP.NET MVC 4 application

Because it's easy and convenient

I am currently working on an MVC4 project that allows users to authenticate through OpenID. I don’t think I need to convince anyone about the benefits for both parties that come with that. Users don’t have to register at your site, and you have less of those tedious account maintance tasks.

Although it’s apparently coming later on as a built-in feature into the Visual Studio templates (Damien Edwards showed that stuff for Web Forms during aspConf), let me show how you can very quickly add simple OpenID support to your MVC4 application.

More after the jump.

Setting the satge

Let’s start with the basic MVC4 project, Internet template.

First of all you will need the excellent DotNetOpenAuth library, and that’s, obviously, something you should get from Nuget. After installing, you’d see a number of libraries added to your application.

Next thing to do is to grab openid-selector JS library. This can be downloaded from http://code.google.com/p/openid-selector/. Once downloaded, unzip the contents and copy all the images and CSS folders to your app’s “Content” folder. Additionally, copy openid-en.js and openid-jquery.js from the JS folder to your app’s “Scripts.” We only need that, because we will be relying on JQuery only.

This JS library will give our login page a familiar look you know for example from Stackoverflow, and would act as a ready plug and play interface for OpenID authentication.

Adding the CS and JS to the views

Since this is MVC4 we can leverage on bundles. Go to App_Start/BundleConfig.cs and add the following there:

This would allow us to easily embed the openid specific JS/CSS whereever we need them. We need a couple of more minor tweaks before we proceed. In the Scripts/openid-jquery.js change

to

so that it corresponds to our MVC4 project structure. If you are using the standard MVc4 project, go to site.css and remove padding-left and padding-right from the a tag definition, as that would ruin our OpenID layout.

Now let’s add the bundles to our view. Go to Views/Account/Login.cshtml. You can delete the existing FormsAuthentication form entirely, as we will be switching to OpenId authentication. Add our CSS bundle and in the scripts Razor section add our script bundle:

Finally, add the HTML form that will act as our OpenID gateway for our application’s users.

Our front end work is done for now, and we should be getting a nice Login page by now (which, of course, does nothing for the time being).

Implementing OpenID authentication service

Before we start the C# implementation part of this, let me briefly explain how we are going to approach the problem:

1. We will be calling the OpenID provider to authenticate our user
2. The response claim will get wrapped into a custom OpenIdUser object
3. When the user is successfully authenticated we will issue a FormsAuthentication ticket based on that OpenIdUser, this way we will be able to leverage on ASP.NET IPrincipal implementation (HttpContext.Current.User).
4. We will actually have a custom IIdentity implementation as well. This way, you could access the information stored in OpenIdUser from anywhere in the application by just typing User.Identity.OpenIdUser

The model

We will start by creating the OpenIdUser.

The model is rather simple, and has the properties you’d normally expect from the User type of model, except Password of course, which is not needed. We have two constructors, because there are two ways in which we will create this object.

First way is to takee in a string that’s been decrypted from Forms authentication ticket UserData part. This is how we will get the info of the user from the cookie and inject into HttpContext as our IIdentity (will show this later).

The second constructor takes in claims response from OpenId provider, and populates the properties based on that.

We also have a simple ToString method which we’ll use to “serialize” the object into a string and embed this data in the Forms ticket.

The service

Now let’s add the service, which will do all the heavy lifting for us.

We will start off with an interface – although it’s not necessary, it could come in handy if you wish to provide an alternative implementation later on. For example, in this particular tutorial, I will not be saving user information into our database at all – we simply use OpenID to authenticate a user. If you wish to store those OpenIDs in your user repository you might very easily do that.

Moreover, since we have an interface in place, you might want to change the implementation to not piggyback on FormsAuthentication tickets as we will do in this example, but use a completely different IPrincipal implementation.

The interface defines two methods – one to generate a request that is validatable against some openID provider and the other to get user.

Our implementation is as follows:

It’s not very complicated at all. In the constructor we instantiate an OpenIdRelyingParty, which will be our OpenID authentication mediator. When the client calls ValidateAtOpenIdProvider and passes the username to be authenticated, we set up a set of request fields (Claim, in OpenID lingo) which we’d like the OpenID provider to return – in our case Email, FullName and NickName.

Once client has validated (authenticated itself), it can request the user object. This is done by invoking the GetUser method. This method uses a couple of helpers, but the gist is that we obtain the claim through the use of OpenIdRelyingParty. This claim can the be converted into our custom user object.

Finally, the service API exposes one more method (not defined in the interface), and that’s CreateFormsAuthenticationCookie. This creates a FormsAuthentication ticket which can be sent down to the browser.

To make this all work we need one more small extension method for the IAuthenticateRequest.

Let’s see how we can now utilize this service from the MVC4 controller.

The authentication controller

Let’s modify the existing AccountController to suit our needs. We won’t need change password and create user methods at all, so you can safely delete them.

We need to make sure we have our OpenID service available, so we add an OpenIdMembershipService field and a initizalize it in the constructor.

Let’s look at the action which will be used for authentication.

If you recall, ValidateAtOpenIdProvider will return IAuthenticationRequest, which in turn exposes a property RedirectingResponse. This lets us send the user to the external page, at the OpenID provider server (i.e. Google login), where he will need to authenticate himself. Then the view re-renders, so that automatically means we have to put the rest of the login in the default GET version of the public ActionResult Login() action.

So when the view re-renders, we try to get user from our OpenID membership service. If the user is successfully returned – meaning the user has authenticated without any problems, we proceed by issuing a FormsAuthentication ticket and redirecting the user back to the original page on which his Authentication challenged was raised – that’s levering on the standard ASP.NET ReturnUrl query string.

The user should at this point see the following:

We might as well end this tutorial here. If you need very basic authentication mechanism, this should already be OK. I will take it a step further and let’s add a customized IIdentity. This would allow us to have access to our OpenIdUser anywhere within the application by calling User.Identity.

Custom IIdentity

We will implement System.Security.Principal.IIdentity interafce, and add an OpenIdUser property there.

We will inject this custom implementation of Identity into the Principal object using a custom AuthorizeAttribute. You could do that also by registering a handler for PostAuthenticateRequest in Global.asax but that’s a bit of an overkill, as it would execute numerous times, whereas the logic in the attribute will only execute once per request.

This grabs the OpenIdUser object from the FormsAuthentication ticket (or rather its UserData section) and inject it into the HTTP context. As a result, we now can go to our partial view for the login, _LoginPartial.cshtml, and replace the login section with

Now this isn’t anything spectacular yet. But we could also use our custom Identity

As you see, this way we get access to the OpenID properties anywhere we want. Remember to decorate whichever action or controller you wish to be protected by OpenID with OpenIDAuthorize i.e.

As a side note, we don’t need any implementation of logout, since the standard one included in the out of the box AccountController does that for us (it deletes the FormsAuthentication ticket).

Trying it out

Now that we have the application’s pieces in place, let’s try it. If you used the attributes like I did above, you should be challenged right away.

If you try Google, you should see this:

And then login successfully

Same with Yahoo:

Summary

In a few simple steps we have implemented OpenID authentication thanks to the very robust DotNetOpenAuth library. You could obviously take it further, as I mentioned before, perhaps store user’s accounts in some repository of yours. Either way, this should be a decent start and I hope someone someone finds it useful. Source code is below (github).

source on github

Be Sociable, Share!

  • Pingback: Dew Drop – August 2, 2012 (#1,376) | Alvin Ashcraft's Morning Dew

  • http://beletsky.net Alexander Beletsky

    Thanks a lot, I was looking for something like this.

    Even thou it is possible, the implementation looks so much complicated, compared to node,js or RoR OpenID solutions.

  • bubba

    Newbie question here. Using OpenID am I able to login using my FaceBook or Twitter account?

    • Filip W

      Neither Twitter nor FB offer are OpenID providers.

      Instead they use OAuth which is a different concept.
      See the article here http://en.wikipedia.org/wiki/OAuth, it explains the differences nicely.

      Of course you could (and probably should) offer Twitter and FB alongside OpenID providers as an option for your users, but the implementation of that would have to be separate from the solution shown here.

  • http://xdrtas.blogspot.com David Lastra

    Thank you very much! Firefox bookmarks!

  • Here

    I like reading blogs about Membershipservice. You did really good work on here. I’ll bookmark your site. Thanks, Klaus.

  • Simon Crozier

    Your guide says “Once downloaded, unzip the contents and copy all the images and CSS folders to your app’s “Content” folder.”

    If you’re doing by folders your bundle needs to looking the CSS folder not the root of the content folder.

    Change from

    bundles.Add(new StyleBundle(“~/Content/css/openid”).Include(
    “~/Content/openid-shadow.css”,
    “~/Content/openid.css”));

    to

    bundles.Add(new StyleBundle(“~/Content/css/openid”).Include(
    “~/Content/css/openid-shadow.css”,
    “~/Content/css/openid.css”));

    • Filip W

      Simon, it doesn’t have to.

      StyleBundle(“~/Content/css/openid”) is a virtual path (doesn’t need to have actual corresponding folder) – we are telling MVC4 that if this virtual path is called, return a bundle of:
      “~/Content/openid-shadow.css” and
      “~/Content/openid.css” (in turn, these are concrete paths).

      The bundle path could be anything and doesn’t have to exist i.e. StyleBundle(“~/Content/abcdefg/”)

      Then when you render the bundle you just call that virtual path and that would render the files you included.

      You can read about bundling in depth at this excellent tutorial by Rick from MSFT – http://www.asp.net/mvc/tutorials/mvc-4/bundling-and-minification

  • RJ

    Great tutorial. Thank you.

    • Filip W

      Thanks! Appreciate it!

  • Sathis

    I need to Import Aol Email Contacts using Mvc4..

  • Don Starkey

    I get an error at the very end of part one.
    Specifically in the post method of the login method.

    return response.RedirectingResponse.AsActionResult();
    Please help I would love to get this to work.

    Error 1 ‘DotNetOpenAuth.Messaging.OutgoingWebResponse’ does not contain a definition for ‘AsActionResult’ and no extension method ‘AsActionResult’ accepting a first argument of type ‘DotNetOpenAuth.Messaging.OutgoingWebResponse’ could be found (are you missing a using directive or an assembly reference?) C:\EcommerceApplication\EcommerceApplication\EcommerceApplication\Controllers\AccountController.cs 39 53 EcommerceApplication

    Great post, I am so close to making this work. Any Help is greatly appreciated.

    • babiloba

      Just add:

      using DotNetOpenAuth.Messaging;

  • babiloba

    Great tutorial:)
    One question. How do you get the fullname??

    • babiloba

      Solved it…

      The question was directed @ goolge login and got it to work by using fetchrequest instead of claimrequest
      and same for the response part

  • Martina

    Thanks! Really good help. I’m using it only for the google account provider.

    I was trying to add a bussiness error for the login, let’s say, besides being a valid google user which is checked by the provider, I’d like to reject all those users who are not valid (I can easily check this with the email address).
    The problem is, wherever I set the validation, either it still logs in or it loops in the login view without allowing me to try with another account.
    So, I basically need to know where the IF sentence should go and how to prevent the guy from logging in.

    Some tip would be great!
    Thanks in advance.

  • Martina Cortés

    Hey! Thanks a lot for the post, it really helped :) Absolutely clear for a beginner like me.
    Still need some extra help. I need to add some bussiness validation so as to reject users who don’t have a certain email domain (using google but with @privatedomain.com accounts).

    Where should I place that validation? Wherever I try, either it allows the login eitherway or it simply doesn’t show the google input view again.
    Thanks!!

  • http://twitter.com/aarnott Andrew Arnott

    Thanks for writing up this tutorial. But I need to call out a serious security flaw with part of your code. You’re using email address as the username when you log the user in. The OpenID protocol does *not* guarantee any assurance on email address and as you’ve implemented it, it’s fairly trivial to impersonate any user on your site. Instead, you really must use the ClaimedIdentifier as the username. You can certainly *display* the email address to the user as if that were their username, but for all user identity checks in your system you must use ClaimedIdentifier.

  • Pingback: IAuthenticationResponse.GetExtensionlt;ClaimsResponsegt; always returning null | Code and Programming

  • distantcam

    I had some issues with an OpenID provider that only populated the FullName field. This caused several null ref exceptions.

    The way I fixed it was to update the OpenIdUser.addClaimInfo() method like this.

    private void addClaimInfo(ClaimsResponse claim, string identifier)
    {
    Email = claim.Email ?? “”;
    FullName = claim.FullName ?? “”;
    Nickname = claim.Nickname ?? claim.Email ?? claim.FullName ?? “”;
    IsSignedByProvider = claim.IsSignedByProvider;
    ClaimedIdentifier = identifier;
    }

  • Hiral Panchal
  • Pingback: MVC4 Forms Authentication Custom Identity Cast not working | TechwikiHow

  • Pingback: Art website – site management | Argument Out Of Range