OpenID and OAuth using DotNetOpenAuth in ASP.NET MVC

I have an ASP.NET MVC application that I would like to have both public and private features. Typically this is done by implementing an authentication / authorization gateway to gain access to the private features. Previously I blogged on an implementation using the ASP.NET membership / role providers with an XML-based membership. This blog post will detail how to use OpenID and OAuth to provide an authentication layer.

  • Authentication – You are who you say you are. The application knows your identity.
  • Authorization – Based upon your identity (via Authentication), the application provides access to features. Public access is provided to base features. Private access to enhanced features.

Here is a screencast showing the implementation in action.

For more information on OpenID and OAuth check out these links:

About OAuth

About OpenID

OAuth-OpenID: You’re barking up the wrong tree if you think they’re the same thing

OpenID / OAuth Workflow

The web application starts by presenting an area that allows the user to select between various OpenID / OAuth providers. The following screenshot shows this area.

imageThis provides a mechanism for the user to select one of the OpenId (OpenID, myOpenID, Google, Yahoo) or OAuth (Twitter) providers. Clicking on one of the providers starts the authentication process. Both OpenID and OAuth use a series of redirects to get the user authenticated. The key is that the authentication occurs on the provider’s site and that’s where the passwords are stored. Clicking one of the above providers results in a post to our application’s server. Our server then redirects to the provider’s server (after possible adding information to the post data). Here is a screenshot of the Twitter authentication page after the redirect to the Twitter provider.

imageClicking the “Allow” button causes post to the Twitter servers and another redirect. This time back to our application’s server. This redirect includes information about you that is supplied by the OpenID / OAuth providers. At minimum the redirect supplies a ‘username’ back to the application.

Our application can then look up the ‘username’ in the database to determine if the user is allowed access. Users that are allowed access will have the above login area swapped out for a panel that provides access to features only available to authenticated users. The following screenshot is an example.

imageNow that we have covered the workflow, let’s take a look at some code. Since we have just looked at the presentation layer, let’s start with the ASP.NET MVC views.

Views, CSS and JavaScript

The logic to display either the login screen or the panel with features for authenticated users is in the Master Page. The following code simply looks to determine if the user is authenticated. If not, it inserts the login partial view. Otherwise, it inserts in the panel partial view.

<div id="login">
    <% if (Request.IsAuthenticated) { %>
        <% Html.RenderPartial("_UserPanel"); %>
    <% } else { %>
        <% Html.RenderPartial("_Login"); %>
    <% } %>
</div>

The panel partial view is provided by the following markup.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>

<div id="user-panel-wrap">
    <span class="left"><%= Html.Encode( Session["FullName"] ) %></span>
    <span class="right">
        <ul>
            <li><%= Html.ActionLink("Add Truck", "Add", "Home") %></li>
            <li><%= Html.ActionLink("Log Off", "LogOff", "Account") %></li>
        </ul>
    </span>
</div>

The above code inserts a ‘div’ containing two ‘spans’. The first ‘span’ is ‘floated left’ (via the CSS class left) and contains the user’s full name. The full name is stored in the ‘Session’ dictionary by the Controller (more later). The second ‘span’ is ‘floated right’ (via the CSS class right) and contains an unordered list of links that provide access to features available to authenticated users only. Be sure to mark these action methods with the ‘Authorize’ attribute otherwise users could directly navigate to the URL and access the feature.

The following CSS styles the panel partial view.

.left   { float: left; }
.right  { float: right;}

#user-panel-wrap
{
    background-color: #000;
    margin: 10px 0;
    padding: 10px;
    color: #fff;
    text-align: right;
    overflow: auto;
}
#user-panel-wrap ul
{
    display: inline-block;
    list-style: none;
}
#user-panel-wrap ul li
{
    float: left;
    margin: 3px 10px;
}

The login partial view markup is shown below.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>

<div id="openid-wrap">
    <form id="openid-form" method="post" action="<%= Url.Action("OpenId", "Account") %>">
        <p>Registered users can <strong>view and add private trucks</strong>. Otherwise feel free to browse
        the <strong>public</strong> trucks in the list below.</p>
        <p>Sign in using one of the providers below.</p>
        <div id="openid-providers">
            <ul>
                <li id="openid"></li>
                <li id="myopenid"></li>
                <li id="twitter"></li>
                <li id="google"></li>
                <li id="yahoo"></li>
            </ul>
        </div>
        <div id="other">
            <h3>Your OpendID Provider</h3>
            <input name="openIdUrl" id="openIdUrl" type="text" />
            <a id="signin">Sign In »</a>
        </div>
    </form>
</div>

The outer ‘div’ wraps a ‘form’ area that contains two child ‘div’ elements. The first child ‘div’ contains an unordered list of providers. This will be styled up using CSS and an image sprite to render the button images. The second child ‘div’ provides an ‘input’ text element that allows the user to enter the URL to any OpenID provider. The following CSS styles this partial view.

#openid-wrap
{
    background-color: #000;
    padding: 10px;
    color: #fff;
    margin-bottom: 10px;
}
#openid-wrap p
{
    font-size: 18px;
    text-align: center;
    margin: 5px 140px 14px;
    line-height: 30px;
}
#openid-wrap form
{
    margin: 3px;
}
#openid-wrap div#other
{
    display: none;
    text-align: center;
    margin: 10px 40px;
    padding: 10px;
    color: #000;
    background-color: #ddd;
    border: 1px solid #888;
    font-size: 18px;
}
#openid-wrap div#other h3
{
    color: #333;
    font-weight: bold;
    margin: 0 0 3px 0;
}
#openid-wrap input
{
    border: 2px solid #fba424;
    height: 36px;
    width: 500px;
}
#openid-wrap div#other a
{
    background-color: #333;
    color: #eee;
    border: 1px solid #666;
    padding: 10px 15px;
    display: inline-block;
}
#openid-wrap div#other a:hover
{
    background-color: #222;
    color: #fff;
    text-decoration: none;
    cursor: pointer;
}
#openid-providers
{
    overflow: auto;
    text-align: center;
}
#openid-providers ul
{
    display: inline-block;
    list-style: none;
}
#openid-providers li
{
    margin: 10px;
    padding: 0;
    float: left;
    width: 150px;
    height: 50px;
    background: transparent url('../images/openid_sprite.png') no-repeat 0 0;
}
li#openid       { background-position: 0 0; }
li#myopenid     { background-position: 0 -50px; }
li#twitter      { background-position: 0 -100px; }
li#facebook     { background-position: 0 -150px; }
li#google       { background-position: 0 -200px; }
li#yahoo        { background-position: 0 -250px; }
li#openid:hover       { background-position: -150px 0; cursor: pointer; }
li#myopenid:hover     { background-position: -150px -50px; cursor: pointer; }
li#twitter:hover      { background-position: -150px -100px; cursor: pointer; }
li#facebook:hover     { background-position: -150px -150px; cursor: pointer; }
li#google:hover       { background-position: -150px -200px; cursor: pointer; }
li#yahoo:hover        { background-position: -150px -250px; cursor: pointer; }

Towards the bottom the background image sprite for the provider buttons is set and individual offsets / hover effects are configured. Here is the image sprite that I am using.

openid_spriteThe left column is the normal button faces and the right column provides a subtly different (maybe too subtle) hover image.

The glue that makes this view work is the following JavaScript (using jQuery of course).

<script type="text/javascript">
    var providers = {
        myopenid: { action: '<%= Url.Action("OpenId", "Account") %>', url: 'http://myopenid.com' },
        twitter: { action: '<%= Url.Action("OAuth", "Account") %>' },
        facebook: { action: '<%= Url.Action("OpenId", "Account") %>', url: 'http://facebook.com' },
        google: { action: '<%= Url.Action("OpenId", "Account") %>', url: 'http://www.google.com/accounts/o8/id' },
        yahoo: { action: '<%= Url.Action("OpenId", "Account") %>', url: 'http://yahoo.com' }
    };
    $("#openid").click(function () {
        toggleOther();
    });
    $("#signin").click(function () {
        $("#openid-form").submit();
    });

    $("li").not("#openid").click(function () {
        $("#other").hide(500);
        isHidden = true;
        var id = $(this).attr("id");
        var provider = providers[id];
        if (provider.action != undefined) {
            $("#openid-form").attr("action", provider.action);
        }
        if (provider.url != undefined) {
            $("#openIdUrl").val(provider.url);
        }
        $("#openid-form").submit();
    });

    var isHidden = true;
    function toggleOther() {
        if (isHidden) {
            $("#other").slideDown(500);
        } else {
            $("#other").slideUp(500);
        }
        isHidden = !isHidden;
    }

</script>

The first bit of JavaScript creates a JSON object containing provider information. Each provider can have an ‘action’ and/or a ‘url’ member. The ‘action’ member defines the URL that the ‘form’ should post to when submitted. Notice there are different URLs for OpenID and OAuth. The ‘url’ member provides the URL to the OpenID provider.

The ‘toggleOther’ method (at the bottom) hides or shows the ‘div’ with the ID ‘other’ by using a slide up/down animation. The essence of the above JavaScript is to set the ‘input’ text box to contain the appropriate URL to the OpenID provider and then submit the form to the desired action method. This is done in the click handler for the ‘li’ elements and uses the JSON object.

Controller

Before discussing the action methods of the controller, you will need to get the DotNetOpenAuth library. Once you download it add a reference to it from your web application project. If you want to get twitter connected up be sure to also download the ‘TwitterConsumer’ and ‘InMemoryTokenManager’ implementations. These implementations are C# code files that you will need to add to your project as well.

I have named the controller ‘AccountController’ and the following code shows the constructor and private fields.

static private readonly OpenIdRelyingParty Openid = new OpenIdRelyingParty();
static private readonly InMemoryTokenManager TokenManager
        = new InMemoryTokenManager("consumerKey", "consumerSecret");

private readonly IFormsAuthentication _formsAuth;
private readonly IIntKeyedRepository<User> _userRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger _logger;

public AccountController(
    IFormsAuthentication formsAuthentication,
    IIntKeyedRepository<User> userRepository,
    IUnitOfWork unitOfWork,
    ILogger logger)
{
    _formsAuth = formsAuthentication;
    _userRepository = userRepository;
    _unitOfWork = unitOfWork;
    _logger = logger;
}

The application uses Ninject as a dependency injection container (thus the missing parameterless controller constructor). Ninject will provide implementations of all the constructor parameters. References to these parameters are then tucked away into the ‘readonly’ fields.

The ‘OpenIdRelyingParty’ instance is an object implemented by the DotNetOpenAuth library that is used for the OpenID authentication. The ‘InMemoryTokenManager’ is used for OAuth authentication with Twitter. Before you can use Twitter as an OAuth provider, you must register your app with Twitter. As a result of this registration Twitter will issue a ‘consumerKey’ and a ‘consumerSecret’ to substitute as the ‘InMemoryTokenManager’ parameters.

From our JavaScript discussions remember that OpenID and OAuth are submitted to two separate action methods. First let’s look at the action method that handles OpenID authentication.

public ActionResult OpenId(string openIdUrl)
{
    var response = Openid.GetResponse();
    if (response == null)
    {
        // User submitting Identifier
        Identifier id;
        if (Identifier.TryParse(openIdUrl, out id))
        {
            try
            {
                var request = Openid.CreateRequest(openIdUrl);
                var fetch = new FetchRequest();
                fetch.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
                fetch.Attributes.AddRequired(WellKnownAttributes.Name.First);
                fetch.Attributes.AddRequired(WellKnownAttributes.Name.Last);
                request.AddExtension(fetch);
                return request.RedirectingResponse.AsActionResult();
            }
            catch (ProtocolException ex)
            {
                _logger.Error("OpenID Exception...", ex);
                return RedirectToAction("Login");
            }
        }
        _logger.Info("OpenID Error...invalid url. url='" + openIdUrl + "'");
        return RedirectToAction("Login");
    }

    // OpenID Provider sending assertion response
    switch (response.Status)
    {
        case AuthenticationStatus.Authenticated:
            var fetch = response.GetExtension<FetchResponse>();
            string firstName = "unknown";
            string lastName = "unknown";
            string email = "unknown";
            if(fetch!=null)
            {
                firstName = fetch.GetAttributeValue(WellKnownAttributes.Name.First);
                lastName = fetch.GetAttributeValue(WellKnownAttributes.Name.Last);
                email = fetch.GetAttributeValue(WellKnownAttributes.Contact.Email);
            }
            return CreateUser(response.ClaimedIdentifier, firstName, lastName, email);
        case AuthenticationStatus.Canceled:
            _logger.Info("OpenID: Cancelled at provider.");
            return RedirectToAction("Login");
        case AuthenticationStatus.Failed:
            _logger.Error("OpenID Exception...", response.Exception);
            return RedirectToAction("Login");
    }
    return RedirectToAction("Login");
}

This action method is called twice. The first time by our form being submitted by the user. The second time is a call back (redirect) from the OpenID provider. The first two lines of the method determine which stage we are in. The code inside the ‘if’ statement is executed when the user submits the form. This code creates an OpenID request and redirects to the OpenID provider. Notice that we are requesting first and last names along with an email address. If the user has configured their OpenID provider to share this info it will be available to us.

The OpenID provider then redirects their response back to our action method. This time, there will be a value for the ‘response’ The code inside the ‘switch’ statement processes that response. If the user is authenticated, the username, first & last name and email are send to the following ‘CreateUser’ action method.

private ActionResult CreateUser(string userName, string firstName, string lastName, string email)
{
    User user = _userRepository.FindBy(x => x.UserName == userName);
    if(user==null)
    {
        user = new User
                   {
                       UserName = userName,
                       FirstName = firstName,
                       LastName = lastName,
                       Email = email
                   };
        return View("Create", user);
    }
    Session["UserName"] = userName;
    string fullName = user.FirstName + " " + user.LastName;
    Session["FullName"] = fullName;
    _formsAuth.SignIn(fullName, false);
    return RedirectToAction("Index", "Home");
}

This code checks to see if this user exists in the local database. If the user is not found, the ‘Create’ view is sent to the browser. This gives our application an opportunity to collect and validate the first & last name and email values. I will not show that view because it becomes application specific as to what additional information you collect from each user. I will also not show the action method that the ‘Create’ view posts back to.

If after the OpenID authentication the user was found in the database, the ‘username’ and ‘fullname’ are stored in the ‘Session’ cache, the user is signed in (cookie set) and a final redirect back to the ‘Index’ action method of the ‘Home’ page. This results in showing the panel that provides access to features only available to authenticated users.

The OAuth action methods are shown below. The ‘OAuth’ action method is called when the user submits the form. This uses the DotNetOpenAuth library to redirect (on the Channel.Send method) to the OAuth provider. Notice this code explicitly configures the callback URL.

public ActionResult OAuth(string returnUrl)
{
    var twitter = new WebConsumer(TwitterConsumer.ServiceDescription, TokenManager);
    var callBackUrl = new Uri(Request.Url.Scheme + "://" + Request.Url.Authority + "/Account/OAuthCallback");
    twitter.Channel.Send(twitter.PrepareRequestUserAuthorization(callBackUrl, null, null));

    return RedirectToAction("Login");
}

public ActionResult OAuthCallback()
{
    var twitter = new WebConsumer(TwitterConsumer.ServiceDescription, TokenManager);
    var accessTokenResponse = twitter.ProcessUserAuthorization();
    if(accessTokenResponse!=null)
    {
        string userName = accessTokenResponse.ExtraData["screen_name"];
        return CreateUser(userName, null, null, null);
    }
    _logger.Error("OAuth: No access token response!");
    return RedirectToAction("Login");
}

The OAuth provider then redirects to the specified callback URL (‘OAuthCallback’ in this case) where the response is processed. A similar process is followed as in the OpenID case. Authenticated responses use the ‘CreateUser’ action method (same as for OpenID and shown above).

Summary

Integrating OpenID and OAuth into your application is made very easy by using the DotNetOpenAuth library. There are a number of services popping up that provide a clean implementation of OpenID / OAuth authentication that you can pay to include in your project. The advantage is that they will maintain the compatibility with OpenID / OAuth as it evolves and are able to provide you statistics on access to your site. Otherwise, using the DotNetOpenAuth library and wiring things up your self is not that bad. If you find the DotNetOpenAuth library beneficial and if you feel so inclined to give back, they provide a way to donate on their page.

Comments
  1. Criação de Site
  2. Andi Sumartono
  3. Andi
  4. Bob Cravens
  5. Alyson Magbitang
  6. Mealiaknittee
  7. Sosh
  8. BatsIhor
  9. Don Smith
  10. Gareth Evans
    • rcravens
  11. Maxi
  12. uri
  13. Maxi
  14. dzień matki
  15. Stuart Clark
  16. Michael Cera
  17. German Catarino
  18. oshadha
    • oshadha
      • Sjaak
  19. Buy Genf20
  20. ulfat
  21. Evan
  22. Kizi 2 Games
  23. Martin
  24. ibbcgroup.com
  25. Kizi Game
  26. komandosiaki
  27. http://www.shameme.net/
  28. serwery csgo
  29. Robbi Edison

Leave a Reply

Your email address will not be published. Required fields are marked *

*