1

I have a situation where we use the ASP.NET Core Identity Framework for the Intranet system with hooks into an old CRM database (this database can't be changed without monumental efforts!).

However, we're having customers login to a separate DBContext using identity framework, with an ID to reference back to the CRM. This is in a separate web app with shared projects between them.

This is cumbersome and causes issues when customers are merged in the CRM, or additional people are added to an account etc. Plus we do not need to use roles or any advanced features for the customer login.

So I was thinking to store the username and password in the CRM with the following process:

  1. Generate a random random password.
  2. Use the internal database ID as the salt.
  3. Store the Sha256 hash of the "salt + password" in the password field.

When a customer logs in, we:

  1. Check the Sha256 hash against the salt and given password
  2. If successful, store a session cookie with the fact the customer is logged in: _session.SetString("LoggedIn", "true");
  3. Each request to My Account will use a ServiceFilter to check for the session cookie. If not found, redirect to the login screen.

Questions:

  1. Is this secure enough?
  2. Should we generate a random salt? If stored in the customer table how would it be different to the internal (20 character) customer ID?
  3. Is there a way for the server session cookie to be spoofed? Should we store a hash in the session which we also check on each action?
5
  • Could you try to explain again, maybe draw a network diagram. Are you saying the user will sign into the .NET intranet site, which in turn gets data from the CRM in the background? Or do users interact with CRM directly? Commented Aug 4, 2020 at 11:35
  • This isn't clear: Store the Sha256 hash of the "salt + password" in the password field.. Is that storing in the Identity database or the CRM database? Commented Aug 4, 2020 at 11:37
  • The salt and hash would be in the CRM database, either as a single field concatenated (like Identity framework). Commented Aug 4, 2020 at 12:15
  • @buhbuh the customer would interact with a public asp.net site with no interface to the intranet, so just asp.net core and the CRM database only, so no use of identity framework Commented Aug 4, 2020 at 12:17
  • Reading the not to roll your own security doesn't impact this I'd think, as it'll be using and off the shelf Salt and hash function (good call on the crypto salt), only use parameterised queries and all work done server side so it'll be leveraging existing security tech rather than trying to write our own Commented Aug 4, 2020 at 20:49

2 Answers 2

3
+100
  1. Is this secure enough?

Generally roll-your-own security is a bad idea because it won't have faced as much scrutiny as an industry standard like Identity Framework. If your application is not life-or-death then maybe this is enough.

  1. Should we generate a random salt?

Yes, salts should always be random. One reason is that when a user changes their password, back to a previous password, if the salt is constant too, then you would get the same hash again, which could be detected.
Another reason is that we don't want the salts to be predictable or sequential. That would make it easier for hackers to generate rainbow tables.

If stored in the customer table how would it be different to the internal (20 character) customer ID?

I suppose if your customer ID is already a long random guid then that might not matter exactly, but best to play it safe, with cryptographically random disposable salts. Look at solutions which use RNGCryptoServiceProvider to generate the salt.

Is there a way for the server session cookie to be spoofed?

I don't think a hacker could create a new session just by spoofing. They would need the username & password.
But they could highjack an existing session using Cross-Site Request Forgery.

Should we store a hash in the session which we also check on each action?

I don't think that would help. Your _session.SetString("LoggedIn", "true") value is already stored on the server and is completely inaccessible from the client. The client only has access to the session cookie, which is just a random id. If that LoggedIn session value is true, then a hash wouldn't make it extra true.

Sign up to request clarification or add additional context in comments.

5 Comments

It's a customer account so it does matter in that sense however identity framework under the hood just has the session cookie set right? The security's is with the roles and the strength of the passwords, plus the config to be able to setup password rules etc... Right?
That's right. From the clients point of view it is just one cookie.
In the roll your own security issue, I fully understand but if we use out of the box hashing and a means to get the salt as you suggest then really we're not rolling our own security, just ensuring we follow best practise, and that the users session to their account is secured - or am I missing something?
Identity provides a lot of features beyond a hash. e.g. Resetting passwords. Email passwords to users. Lockout after 5 failed access attempts. Password strength validation. Protecting static assets. Email address confirmation. Cross-Site Request Forgery is a big one. The main argument for never roll-your-own is that you will be hacked using an idea that you haven't thought of. Even you if you fix all the issues listed here, there will always be one more way you might get attacked.
Cory Doctorow's famous Schneier's Law: "any person can invent a security system so clever that she or he can't think of how to break it." The security code you write yourself instead of using out of the box, the more risk you introduce.
2

Last year I made a custom IUserPasswordStore for a customer. This solution involved Microsoft.AspNetCore.Identity.UserManager which handles password hashing behind the scenes, no custom password handling required. You will be responsible for storing hashed password in db along with other user properties.

I cannot publish the code in its entirety, it is not my property, but I can sketch up the main parts.

First, we need the IdentityUser:

public class AppIdentityUser : IdentityUser<int>
{
    public string Name { get; set; }
}

Then an implementation if IUserPasswordStore

public class UserPasswordStore : IUserPasswordStore<AppIdentityUser>
{
    private readonly IUserRepo _userRepo; // your custom user repository
    private readonly IdentityErrorDescriber _identityErrorDescriber;

    public UserPasswordStore(IUserRepo userRepo, IdentityErrorDescriber identityErrorDescriber)
    {
        _userRepo = userRepo;
        _identityErrorDescriber = identityErrorDescriber;
    }

    public Task<IdentityResult> CreateAsync(AppIdentityUser user, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        if (user == null)
        {
            throw new ArgumentNullException(nameof(user));
        }

        // if email exists, fail
        if (_userRepo.GetByEmailAddress(user.Email) != null)
        {
            return Task.FromResult(IdentityResult.Failed(_identityErrorDescriber.DuplicateEmail(user.Email)));
        }

        // ... convert AppIdentityUser to model class
        // 
        _userRepo.Save(userModel);
        return Task.FromResult(IdentityResult.Success);
    }

    ... implementation of the rest of IUserPasswordStore<AppIdentityUser> comes here
}

Inject this into code for identity user CRUD-operations, e.g. user management controller:

UserManager<AppIdentityUser>

Sample code for changing password (sorry for the nesting)

        var result = await _userManager.RemovePasswordAsync(identityUser);
        if (result.Succeeded)
        {
            result = await _userManager.AddPasswordAsync(identityUser, model.Password);
            if (result.Succeeded)
            {
                var updateResult = await _userManager.UpdateAsync(identityUser);
                if (updateResult.Succeeded)
                {
                   ... do something
                }
            }
        }

Inject this into LoginController:

SignInManager<AppIdentityUser>

We also need an implementation of

IRoleStore<IdentityRole>. 

If authorization is not required, leave all methods empty.

In Startup#ConfigureServices:

services.AddIdentity<AppIdentityUser, IdentityRole>().AddDefaultTokenProviders();
services.AddTransient<IUserStore<AppIdentityUser>, UserPasswordStore>();
services.AddTransient<IRoleStore<IdentityRole>, RoleStore>();
services.Configure<CookiePolicyOptions>(options => ...
services.Configure<IdentityOptions>(options => ...
services.ConfigureApplicationCookie(options => ...

In Startup#Configure:

app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();

See also https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-custom-storage-providers?view=aspnetcore-3.1

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.