4

Background: I've a Web App that offers a service to my customers.

Motivation: Now I want to expose that service with the help of an API (WCF & Web API). The consumers of the service will need to authenticate.

The Problem: Most of the consumers of the API will be from my customers of the Web App.

I don't want that one client will have 2 passwords, one for the Web App and one for the API.

How can I share the Web App (MVC5) DB with other projects? like WCF for example.

I need in my WCF two methods that will run exactly like the Web App:

  • Register.
  • Login.

This methods are implement in my project as follow:

Register:

 public async Task<ActionResult> Register(RegisterViewModel model)
    {
        if (ModelState.IsValid)
        {
            var user = new ApplicationUser { UserName = model.UserName, Email = model.Email, OrganizationID = "10", DateJoin = DateTime.Now, LockoutEndDateUtc=DateTime.UtcNow.AddYears(5),LockoutEnabled=false};
            var result = await UserManager.CreateAsync(user, model.Password);
            if (result.Succeeded)
            {

                IdentityResult resultClaim = await UserManager.AddClaimAsync(user.Id, new Claim("OrgID", "10"));

                if(resultClaim.Succeeded)
                {
                    UserManager.AddToRole(user.Id, "guest");
                    await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
                    return RedirectToAction("Index", "Home");
                }


            }
            AddErrors(result);
        }

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

Login:

  public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
    {

        if (!ModelState.IsValid || User.Identity.IsAuthenticated)
        {
            return View(model);
        }

        // This doesn't count login failures towards account lockout
        // To enable password failures to trigger account lockout, change to shouldLockout: true
        var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
        switch (result)
        {
           case SignInStatus.Success:
                Session["Timezone"] = model.offSet;
                    return RedirectToLocal(returnUrl);

            case SignInStatus.LockedOut:
                return View("Lockout");

            case SignInStatus.RequiresVerification:
                return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });

            case SignInStatus.Failure:
            default:
                ModelState.AddModelError("", "Invalid login attempt.");
                return View(model);
        }
    }
Ron
  • 1,744
  • 6
  • 27
  • 53

2 Answers2

0

You can create a seperate Identity Database and have all your users authenticate against that. When you create the UserManager/RoleManager etc you can point the connection string to the Identity Database

Nathan Fisher
  • 7,961
  • 3
  • 47
  • 68
  • So you suggest to create a new DB (Identity DB) and point the two component to the new DB ? What would I do next month when I want to integrate another component to the DB ? say Web API ? I would need to create another new DB ? – Ron May 14 '15 at 09:35
  • I'm not sure I follow. Your question was about primarily about sharing Identity across different projects(Web API, WCF, etc) and how to accomplish that. All your applications can share the same database for Identity and application data if that is what is needed. – Nathan Fisher May 18 '15 at 01:53
0

After reading a couple of articles I understand how the passwords verifier works and with a little help from the open source code of Microsoft I manage to build a class that take care of the password verify.

UserValidation class:

 public class UserValidation 
{
    public override void Validate(string userName, string password)
    {
        if (null == userName || null == password)
        {
            throw new ArgumentNullException();
        }

        string hashPassword = DataQueries.GetHashPassword(userName);

        if (!VerifyHashedPassword(hashPassword, password))
            throw new FaultException("Unknown Username or incorrect Password");

    }

    private static string HashPassword(string password)
    {
        byte[] salt;
        byte[] buffer2;
        if (password == null)
        {
            throw new ArgumentNullException("password");
        }
        using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, 0x10, 0x3e8))
        {
            salt = bytes.Salt;
            buffer2 = bytes.GetBytes(0x20);
        }
        byte[] dst = new byte[0x31];
        Buffer.BlockCopy(salt, 0, dst, 1, 0x10);
        Buffer.BlockCopy(buffer2, 0, dst, 0x11, 0x20);
        return Convert.ToBase64String(dst);
    }

    private static bool VerifyHashedPassword(string hashedPassword, string password)
    {
        byte[] buffer4;
        if (hashedPassword == null)
        {
            return false;
        }
        if (password == null)
        {
            throw new ArgumentNullException("password");
        }
        byte[] src = Convert.FromBase64String(hashedPassword);
        if ((src.Length != 0x31) || (src[0] != 0))
        {
            return false;
        }
        byte[] dst = new byte[0x10];
        Buffer.BlockCopy(src, 1, dst, 0, 0x10);
        byte[] buffer3 = new byte[0x20];
        Buffer.BlockCopy(src, 0x11, buffer3, 0, 0x20);
        using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, dst, 0x3e8))
        {
            buffer4 = bytes.GetBytes(0x20);
        }
        return ByteArraysEqual(buffer3, buffer4);
    }

    private static bool ByteArraysEqual(byte[] b1, byte[] b2)
    {
        if (b1 == b2) return true;
        if (b1 == null || b2 == null) return false;
        if (b1.Length != b2.Length) return false;
        for (int i = 0; i < b1.Length; i++)
        {
            if (b1[i] != b2[i]) return false;
        }
        return true;
    }

The idea behind Microsoft password verify algorithm is:

The string that stored in the DB contains the hash of the password with salt, here is an example: We have a string in the DB : 123456789, part of the string is the hash password and part of it is the salt, in our example lets say 123456 is the hash password and the 789 is the salt. (the number of characters that hold the salt is always the same in Microsoft algorithm but could be changed manually)

Ron
  • 1,744
  • 6
  • 27
  • 53