Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using OpenID/OpenAuth in MVC3 app with overridden authentication method

We override the basic authentication in an MVC3 application by calling a webservice with the user's credentials and returning a WCF structure that contains the user's ID, a "LogonTicket". This LogonTicket is used to "authenticate the user for each call made to the webservice.

Now, we override by replacing the defaultProvider in the Web.config. All we do in this overridden provider is to override the ValidateUser() function. That is where we call the web service with their credentials and return the "LogonTicket".

This is the LogOn() function from our AccountController, essentially the base code from the template:

public ActionResult LogOn(LogOnModel model)
{
    string ReturnUrl = "";
    if (HttpContext.Request.UrlReferrer.Query.Length > 11)
    {
        ReturnUrl = Uri.UnescapeDataString(HttpContext.Request.UrlReferrer.Query.Substring(11));
    }
    if (ModelState.IsValid)
    {
        if (Membership.ValidateUser(model.UserName, model.Password))
        {
            FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
            if (Url.IsLocalUrl(ReturnUrl) && ReturnUrl.Length > 1 && ReturnUrl.StartsWith("/")
                && !ReturnUrl.StartsWith("//") && !ReturnUrl.StartsWith("/\\"))
            {
                return Redirect(ReturnUrl);
            }
            else
            {
                return RedirectToAction("Index", "Home");
            }
        }
        else
        {
            ModelState.AddModelError("", "The user name or password provided is incorrect.");
        }
    }

    // If we got this far, something failed, redisplay form
    ViewBag.MainWebsite = MainWebsite;
    return View(model);
}

This is the overridden ValidateUser() function from our new default provider:

public override bool ValidateUser(string username, string password)
{
    MyServiceClient mps = new MyServiceClient();
    string sha1password = HashCode(password);
    LogonInfo logonInfo = mps.GetLogonTicket(username, sha1password);
    if (logonInfo.LogonTicket != "" && logonInfo.LogonTicket != "0") 
    {
    // Authenticated so set session variables
        HttpContext.Current.Session["LogonTicket"] = logonInfo.LogonTicket;
        HttpContext.Current.Session["ParticipantID"] = logonInfo.ParticipantID;
        return true;
    }
    else
    {
        return false;
    }
}

I'm not really sure how to combine the use of the two, so my questions are:

  1. How can I implement OpenID and Facebook logins and keep my current authentication method?
  2. How can we "map" the OpenID user with our current user DB values? We MUST know so we can retrieve their info. I know we can retrieve their email address but what if their OpenID email is different than the one they use for their record on our site?
  3. Are there any examples of how to do this, anywhere?

Thanks for looking at my question.

like image 230
MB34 Avatar asked Dec 14 '25 17:12

MB34


2 Answers

I have done a project which required multiple log-on possibilities (custom account, Google and Facebook)

In the end your authentication with ASP.NET is entirely dependant on your configuration. (In your case it is FormsAuthentication) this means that FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe); basicly determines everything in regard to your user and where you set this isn't restricted.

You have now basicly the same implementation as we started out with, using a MembershipProvider to handle your own custom account. You only need to expand now to facilitate the openIds. You would have to expand your Controller with various actions for each login type (Now you have ActionResult LogOn() you can add to that for example: ActionResult LogOnOpenId()). Inside that method you basicly call the same code but instead of Membership.ValidateUser(model.UserName, model.Password) you call the OpenId services.

I have provided below an example of our google implementation using dotnetopenauth. The service method uses formsService.SignIn(userId.Value.ToString(), false); which basicly calls FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe); (we only do some custom behaviour there in regard to the SecurityPrincipal but this doesn't affect your Authentication process). You can also see that we make a new account when we receive a new user. To solve your question part 2 we have implemented a profile which can be merged if you can provide another login. This allows our users to keep their account consolidated and use whatever login method they like.

For examples in regard to multiple signons I will refer to the answer of Tomas whom referenced StackExchange as a good example. Also I'd advise you to install MVC4 and VS2012 and just do a File > New Project. The newest default template of MVC includes openid implementation alongside a custom login!

Example google openid implementation:

The controller method:

    public virtual ActionResult LoginGoogle(string returnUrl, string runAction)
    {
        using (var openId = new OpenIdRelyingParty())
        {
            IAuthenticationResponse response = openId.GetResponse();

            // If we have no response, start 
            if (response == null)
            {
                // Create a request and redirect the user 
                IAuthenticationRequest req = openId.CreateRequest(WellKnownProviders.Google);
                var fetch = new FetchRequest();
                fetch.Attributes.AddRequired(WellKnownAttributes.Name.First);
                fetch.Attributes.AddRequired(WellKnownAttributes.Name.Last);
                fetch.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
                fetch.Attributes.AddRequired(WellKnownAttributes.Preferences.Language);
                req.AddExtension(fetch);

                req.RedirectToProvider();

                return null;
            }

            _service.ConnectViaGoogle(response, TempData);
    }

The service method:

    public void ConnectViaGoogle(IAuthenticationResponse response, TempDataDictionary tempData)
    {
        // We got a response - check it's valid and that it's me 
        if (response.Status == AuthenticationStatus.Authenticated)
        {
            var claim = response.GetExtension<FetchResponse>();
            Identifier googleUserId = response.ClaimedIdentifier;
            string email = string.Empty;
            string firstName = string.Empty;
            string lastName = string.Empty;
            string language = string.Empty;

            if (claim != null)
            {
                email = claim.GetAttributeValue(WellKnownAttributes.Contact.Email);
                firstName = claim.GetAttributeValue(WellKnownAttributes.Name.First);
                lastName = claim.GetAttributeValue(WellKnownAttributes.Name.Last);
                language = claim.GetAttributeValue(WellKnownAttributes.Preferences.Language);
            }

            //Search User with google UserId
            int? userId = _userBL.GetUserIdByGoogleSingleSignOnId(googleUserId);

            //if not exists -> Create
            if (!userId.HasValue)
            {
                _userBL.CreateGoogleUser(
                    googleUserId,
                    firstName,
                    lastName,
                    email,
                    language,
                    DBConstants.UserStatus.DefaultStatusId,
                    out userId);
            }

            if (userId.HasValue)
            {
                _userBL.UpdateLastLogon(userId.Value);
                var formsService = new FormsAuthenticationService();
                formsService.SignIn(userId.Value.ToString(), false);
                AfterLoginActions(tempData);
            }
        }
    }

Any questions or comments? I'll gladly hear them.

like image 81
IvanL Avatar answered Dec 17 '25 09:12

IvanL


  1. it should be perfectly possible to have multiple authentications methods. All IIS / ASP.net cares about is the FormsAuthentication cookies. So you would have one set of actions for your standard username/password auth, and another for OpenId. This is at least what I have done on one project.
  2. You can't even trust the openId provider to give you an email address! A common solution to this problem is to allow a user to attach multiple OpenId identifiers (URI's) to the his account after logging in. This is e.g. how StackOverflow works. If this is the first time the user visits the system then you can auto create a new account, or force the user through a signup process.
    When I added the OpenId support in the system mentioned, it had an existing table used to store username and password(users table). I added a new table with a many to one relationship with the users table, and used this to store the URI's.
  3. As mentioned above StackOverflow it self is a good place to start, also there are a lot of good examples in the http://www.dotnetopenauth.net/ project. As far as I know the source of SO is not public, and they are using the dotnetopenauth project.
    This may be to abstract, but this library is a openId (among other things) for the open source orchard CMS: http://orchardopenauth.codeplex.com/

I hope this helps, but if you have any questions then please expand your question with more details.

like image 21
Tomas Avatar answered Dec 17 '25 08:12

Tomas



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!