Authentication with OpenId

A few weeks ago I blogged about the basics of storing web passwords.  I mentioned that the best solution is to avoid storing web passwords and use OpenId to manage user authentication.  I had the chance to play around with OpenId authentication and found the examples to be rather confusing, so today I’m going to write a very simple example of how to use an OpenId provider for user authentication.

DotNet OpenAuth

DotNet OpenAuth is a library for implementing OpenId, OAuth and InfoCards in your .Net applications.  For the example application I simply want to be able to authenticate my users with an OpenId provider (such as Blogger or Google) which means I want to use an OpenId relying party.

The Example

For this example I’m going to create a very very simple picture-sharing site with MVC.  Users will be able to view pictures, but to add pictures you need to register and be logged in with OpenId.  I’m going to use Sqlite as a simple data store.

To get started, I’m going to create a User controller for managing all authentication and registration.  I have already created a very simple login action and view.  I will redirect users to this action if they choose to login or access a part of the site for which authentication is required.

public ActionResult Login()
{
    return View();
}

The login view asks for user for a single input – their OpenId URL.  Note that the same action and view is used for either a user who is registering or simply logging in.

Login or Register View

Now we need to redirect the user to the OpenId provider.  I used the sample snippet from the DotNet OpenAuth documentation.  The only tricky bit is that the OpenId provider will redirect back to the original action after authentication.  We therefore do a POST when the user enters their OpenId details and the OpenId provider will then do a GET on an action with the same name.

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Authenticate(string openId)
{
    if (!Identifier.IsValid(openId))
    {
        ModelState.AddModelError("openId", "The specified openId is invalid");
        return View("Login");
    }
    else
    {
        var openid = new OpenIdRelyingParty();
        IAuthenticationRequest request = openid.CreateRequest(Identifier.Parse(openId));
        var response = request.RedirectingResponse;

        return response.AsActionResult();
    }
}

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Authenticate()
{
    var openid = new OpenIdRelyingParty();
    var response = openid.GetResponse();

    if (response != null)
    {
        switch (response.Status)
        {
            case AuthenticationStatus.Authenticated:
                return CompleteAuthentication(response.ClaimedIdentifier);
            case AuthenticationStatus.Canceled:
                ModelState.AddModelError("openId",
                    "Login was cancelled at the provider");
                break;
            case AuthenticationStatus.Failed:
                ModelState.AddModelError("openId",
                    "Login failed using the provided OpenID identifier");
                break;
        }
    }

    return View("Login");
}

Which is actually quite simple.  The provider will authenticate our user and provide us with a response to indicate the success of the authentication process.  All we need to manage is the identifier.

Now that the user is authenticated, we need to complete the login (or registration process).

private ActionResult CompleteAuthentication(string openId)
{
    var registeredUser = userRepository.GetUsers().WithOpenId(openId);
    if (registeredUser != null)
    {
        Session["User"] = registeredUser;
        return RedirectToAction("Index", "Home");
    }
    else
    {
        Session["OpenId"] = openId;
        return View("Register");
    }
}

So after the user is authenticated with the OpenId provider, we check to see if this user exists in our database.  If the user exists, I store the user in the Session object, otherwise I store the OpenId credentials and ask the user to register.  Once the user provides us with the outstanding registration details (in this case, an e-mail address) I create an entry in the database.

Securing certain actions

To check that certain actions can only be accessed by a logged in user, I created an attribute that can be applied to any action.

public class MustBeLoggedInAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var user = filterContext.HttpContext.Session["User"] as User;
        if (user == null)
        {
            //send them off to the login page

            var url = new UrlHelper(filterContext.RequestContext);
            var loginUrl = url.Content("~/User/Login"); 
            filterContext.HttpContext.Response.Redirect(loginUrl, true);    
        }
    }
}

The use of the Session object

I know not everyone is crazy about using the Session object in this way.  I think that the session object is perfectly suited to this scenario.  The alternative would be to issue a cookie for authentication purposes, but in my opinion it’s neater to simply issue one cookie (which is used to identify the session) and then simply use the session object to store the authentication details.  If you would like to store your session somewhere, encrypt the session cookie or have long-running sessions it’s still only one change you need to make.

Conclusion

As I said in my post on Web passwords – it’s always better to avoid storing passwords in our websites.  OpenId is a safe, secure and proven solution to this problem and the implementation is actually much easier than having to implement your own solution.

Keep in mind I have only implemented the most basic form of OpenId authentication – in my next post I will look at making the login process a little easier for the user by providing a few OpenId options.

Happy coding.