Dotnetopenauth single sign on with custom identity provider
Asked Answered
H

1

12

I'm trying to setup the DotNetOpenAuth samples to have a working SSO solution with a custom provider. I'm using OpenIdProviderMvc sample project which appears to be working fine.

My problems is setting up the "consumer", in this case the OpenIdRelyingPartyMvc sample project, I cannot configure it to use the OpenIdProvider.

I tried to setup an endpoint on the consumer's web.config like this:

<trustedProviders rejectAssertionsFromUntrustedProviders="true">
    <add endpoint="http://localhost:4864/OpenID/Provider" />
</trustedProviders>

But all I get is "No OpenID endpoint found." errors (actually, I'm not quite sure on what to put on the OpenID box...)

This project is virtually undocumented. Can someone point me in the right direction?

At least to have a provider and consumer working and talking to each other?

Harridan answered 17/8, 2013 at 17:47 Comment(1)
For what it's worth : I encountered no problem running the samples ( github.com/DotNetOpenAuth/DotNetOpenAuth ). I did NOT uncomment the trusted providers section, started two instances in VS for the server and the client and just entered localhost:4864 in the OpenId box. By default, I guess the sample client wil accept any openId provider.Rafter
B
7

Let us start :

1- Open Visual Studio 2010 go to File > New > Project > Web > ASP.NET MVC 3 Application:

enter image description here

then Choose Internet Application be sure to have Razor as your View engine and Click Ok:

enter image description here

2- Download Assets folder , it contains DotNetOpenAuth dll and OpenID-Selector files that we will use ,

Feel free if you want to go to these projects and discover them in more details.

Extract it to the folder you want

  a - Add the DotNetOpenAuth.dll to references in your site.

  b- Delete all files/folders in Site Content folder.

  c- Copy Assets Content files/folders to the site Content .

  d- Copy the  Assets Script files to the site Script.

.

Your project will look like this :

enter image description here

3- Go to Views > Shared > _Layout.cshtml and replace the with this new head , we just added the new styles and scripts:

<head>
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" 
     rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")"
     type="text/javascript"></script>
    <link href="@Url.Content("~/Content/openid-shadow.css")"
     rel="stylesheet" type="text/css" />
    <link href="@Url.Content("~/Content/openid.css")" 
     rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/openid-en.js")" 
     type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/openid-jquery.js")" 
     type="text/javascript"></script>
    <script type="text/javascript">
        $(document).ready(function () {
            openid.init('openid_identifier');
        });
    </script>
</head>

4- Go to Models > AccountModels.cs , navigate to public class LogOnModel

and Add OpenID attribute that we will use it to hold the returned OpenID from OpenID-Selector

your class will look like this:

public class LogOnModel
{
    [Display(Name = "OpenID")]
    public string OpenID { get; set; }

    [Required]
    [Display(Name = "User name")]
    public string UserName { get; set; }

    [Required]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

    [Display(Name = "Remember me?")]
    public bool RememberMe { get; set; }
}

navigate to public class RegisterModel and Add OpenID attribute

public class RegisterModel
{

    [Display(Name = "OpenID")]
    public string OpenID { get; set; }

    [Required]
    [Display(Name = "User name")]
    public string UserName { get; set; }

    [Required]
    [DataType(DataType.EmailAddress)]
    [Display(Name = "Email address")]
    public string Email { get; set; }

    [Required]
    [ValidatePasswordLength]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage =
    "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}

Then go to Services section in AccountModels.cs

and Modify the CreateUser and Add GetUser to get the user by OpenID , your Interface

will looks like this:

public interface IMembershipService
{
    int MinPasswordLength { get; }
    bool ValidateUser(string userName, string password);
    MembershipCreateStatus CreateUser(string userName, string password,
                                      string email, string OpenID);
    bool ChangePassword(string userName, string oldPassword, string newPassword);
    MembershipUser GetUser(string OpenID);
}

Add these using to AccountModels.cs

using System.Security.Cryptography;
using System.Text;

Then Add This Function to the AccountModels.cs , this function will be used to convert the OpenID to GUID

Note: feel free to use better hashing to your system , MD5 had some collision issues.

public Guid StringToGUID(string value)
{
    // Create a new instance of the MD5CryptoServiceProvider object.
    MD5 md5Hasher = MD5.Create();
    // Convert the input string to a byte array and compute the hash.
    byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(value));
    return new Guid(data);
}

Also Modify The CreateUser Function to look like this:

public MembershipCreateStatus CreateUser(string userName, string password, 
                                         string email , string OpenID)
{
    if (String.IsNullOrEmpty(userName)) throw 
    new ArgumentException("Value cannot be null or empty.", "userName");
    if (String.IsNullOrEmpty(password)) throw 
    new ArgumentException("Value cannot be null or empty.", "password");
    if (String.IsNullOrEmpty(email)) throw
    new ArgumentException("Value cannot be null or empty.", "email");

    MembershipCreateStatus status;
    _provider.CreateUser(userName, password, email, null, null, true,
                            StringToGUID(OpenID), out status);
    return status;
}

Here we are using the MemberShip ProviderUserKey to store the OpenID and the trick here that we convert the OpenID string to GUID to be used by CreateUser and GetUser methods.

Now let us add this function to AccountModels.cs that will get the user by OpenID:

public MembershipUser GetUser(string OpenID)
{
    return _provider.GetUser(StringToGUID(OpenID), true);
}

5- go to Views > Account > LogOn.cshtml

replace all the markup with this one ,we are integrating OpenID-Selector to LogOn View:

@model OpenIDMVC3.Models.LogOnModel
@{
    ViewBag.Title = "Log On";
}
<h2>
    Log On</h2>
<p>
    Please enter your username and password. @Html.ActionLink("Register", "Register")
    if you don't have an account.
</p>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript">
</script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"
   type="text/javascript"></script>
<form action=
"[email protected](Request.QueryString["ReturnUrl"])"
 method="post" id="openid_form">
<input type="hidden" name="action" value="verify" />
<div>
    <fieldset>
        <legend>Login using OpenID</legend>
        <div class="openid_choice">
            <p>
                Please click your account provider:</p>
            <div id="openid_btns">
            </div>
        </div>
        <div id="openid_input_area">
            @Html.TextBox("openid_identifier")
            <input type="submit" value="Log On" />
        </div>
        <noscript>
            <p>
                OpenID is service that allows you to log-on to many different websites 
                using a single indentity. Find out <a href="http://openid.net/what/">
                 more about OpenID</a>and <a href="http://openid.net/get/">
                 how to get an OpenID enabled account</a>.</p>
        </noscript>
        <div>
            @if (Model != null)
            {
                if (String.IsNullOrEmpty(Model.UserName))
                {
                <div class="editor-label">
                    @Html.LabelFor(model => model.OpenID)
                </div>
                <div class="editor-field">
                    @Html.DisplayFor(model => model.OpenID)
                </div>
                <p class="button">
                    @Html.ActionLink("New User ,Register", "Register", 
                                         new { OpenID = Model.OpenID })
                </p>
                }
                else
                {
                    //user exist 
                <p class="buttonGreen">
                    <a href="@Url.Action("Index", "Home")">Welcome , @Model.UserName, 
                    Continue..." </a>
                </p>

                }
            }
        </div>
    </fieldset>
</div>
</form>

@Html.ValidationSummary(true, "Login was unsuccessful. Please correct the errors 
                                                    and try again.")
@using (Html.BeginForm())
{
    <div>
        <fieldset>
            <legend>Or Login Normally</legend>
            <div class="editor-label">
                @Html.LabelFor(m => m.UserName)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.UserName)
                @Html.ValidationMessageFor(m => m.UserName)
            </div>
            <div class="editor-label">
                @Html.LabelFor(m => m.Password)
            </div>
            <div class="editor-field">
                @Html.PasswordFor(m => m.Password)
                @Html.ValidationMessageFor(m => m.Password)
            </div>
            <div class="editor-label">
                @Html.CheckBoxFor(m => m.RememberMe)
                @Html.LabelFor(m => m.RememberMe)
            </div>
            <p>
                <input type="submit" value="Log On" />
            </p>
        </fieldset>
    </div>
}

6- Now let us run the project , then click the [Log On] link , you will get like this page:

ws

7- Go to Controllers > AccountController.cs and Add these using:

using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId;
using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
using DotNetOpenAuth.OpenId.RelyingParty;
using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;

Then Add this Attribute to AccountController.cs:

private static OpenIdRelyingParty openid = new OpenIdRelyingParty();

Then Add this Function to AccountController.cs:

[ValidateInput(false)]
public ActionResult Authenticate(string returnUrl)
{
    var response = openid.GetResponse();
    if (response == null)
    {
        //Let us submit the request to OpenID provider
        Identifier id;
        if (Identifier.TryParse(Request.Form["openid_identifier"], out id))
        {
            try
            {
                var request = openid.CreateRequest(
                                        Request.Form["openid_identifier"]);
                return request.RedirectingResponse.AsActionResult();
            }
            catch (ProtocolException ex)
            {
                ViewBag.Message = ex.Message;
                return View("LogOn");
            }
        }

        ViewBag.Message = "Invalid identifier";
        return View("LogOn");
    }

    //Let us check the response
    switch (response.Status)
    {

        case AuthenticationStatus.Authenticated:
            LogOnModel lm = new LogOnModel();
            lm.OpenID = response.ClaimedIdentifier;
            // check if user exist
            MembershipUser user = MembershipService.GetUser(lm.OpenID);
            if (user != null)
            {
                lm.UserName = user.UserName;
                FormsService.SignIn(user.UserName, false);
            }

            return View("LogOn", lm);

        case AuthenticationStatus.Canceled:
            ViewBag.Message = "Canceled at provider";
            return View("LogOn");
        case AuthenticationStatus.Failed:
            ViewBag.Message = response.Exception.Message;
            return View("LogOn");
    }

    return new EmptyResult();
}

8 - Now run the project click [Log On] link and click a provider like Google

it may ask you to sign in or ask you to allow access to your information

you will get a page like this :

enter image description here

As you can see it displays your OpenID and a button that indicate that this is a new user not registered yet,

before hitting [New User ,Register] button we need to modify the Register view and controller to access OpenID information.

9- Go to controllers > AccountController.cs replace the [ActionResult Register ()] by this :

public ActionResult Register(string OpenID)
{
    ViewBag.PasswordLength = MembershipService.MinPasswordLength;
    ViewBag.OpenID = OpenID;
    return View();
}

And Modify the [ActionResult Register(RegisterModel model)] to use OpenID when

creating users:

[HttpPost]
public ActionResult Register(RegisterModel model)
{
    if (ModelState.IsValid)
    {
        // Attempt to register the user
        MembershipCreateStatus createStatus =
        MembershipService.CreateUser(model.UserName, model.Password, 
                                        model.Email,model.OpenID);

        if (createStatus == MembershipCreateStatus.Success)
        {
            FormsService.SignIn(model.UserName, false);
            return RedirectToAction("Index", "Home");
        }
        else
        {
            ModelState.AddModelError("",
            AccountValidation.ErrorCodeToString(createStatus));
        }
    }

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

10- Go to Views > Account > Register.cshtml , replace the markup by this :

@model OpenIDMVC3.Models.RegisterModel
@{
    ViewBag.Title = "Register";
}

<h2>Create a New Account</h2>
<p>
    Use the form below to create a new account. 
</p>
<p>
    Passwords are required to be a minimum of @ViewBag.PasswordLength 
    characters in length.
</p>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" 
  type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"
   type="text/javascript"></script>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true, "Account creation was unsuccessful.
                                            Please correct the errors and try again.")
    <div>
        <fieldset>
            <legend>Account Information</legend>
            @if (ViewData["OpenID"] != null)
            {
            <div class="editor-label">
                @Html.Label("OpenID")
            </div>
            <div class="editor-label">
                @Html.Label((string)ViewBag.OpenID)
            </div>
            }
            <div class="editor-label">
                @Html.LabelFor(m => m.UserName)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.UserName)
                @Html.ValidationMessageFor(m => m.UserName)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.Email)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.Email)
                @Html.ValidationMessageFor(m => m.Email)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.Password)
            </div>
            <div class="editor-field">
                @Html.PasswordFor(m => m.Password)
                @Html.ValidationMessageFor(m => m.Password)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.ConfirmPassword)
            </div>
            <div class="editor-field">
                @Html.PasswordFor(m => m.ConfirmPassword)
                @Html.ValidationMessageFor(m => m.ConfirmPassword)
            </div>

            <p>
                <input type="submit" value="Register" />
            </p>
        </fieldset>
    </div>
}

11- Go to step 8 and let us hit [New User ,Register] button , you will get this :

enter image description here

12- Register any account you want you will get like this page :

enter image description here

13- Click [Log Off] and login again using the same OpenID , you will get like this page:

enter image description here

As you can see the welcome green button detect that this user is registered .

14- Click the green button you will get a page like this :

enter image description here

Congratulation ! , now you had integrated OpenID to your project.

Reference

Bludge answered 20/8, 2013 at 11:1 Comment(2)
Very complete tutorial... The reference link appears to not point to what you might think it does.Ugly
Thanks but, how do I link this to a custom provider? ie, my own authentication provider ?Harridan

© 2022 - 2024 — McMap. All rights reserved.