Search code examples
asp.netazureauthenticationasp.net-identityauth0

How can you use Auth0 with existing ASP.NET Core Identity database?


I'm working on Auth0 integration for an application. I have an existing application that uses ASP.NET Core Identity and a SQL Database currently with the usual tables (AspNetUsers, AspNetRoles, etc).

The problem is that Auth0 currently has custom database templates for working with ASP.NET Membership Provider (MVC3 Universal Providers, and MVC4 Simple Membership) database, but not ASP.NET Core Identity. Since I don't know exactly how to code the Password Hashing necessary to be compatible with my ASP.NET Core Identity database, this is a big problem.

Does anyone have an example of Auth0 custom database scripts for working with an existing ASP.NET Core Identity database? Or, at least the hashing algorithm used to code it myself?

For more info, the ASP.NET Core Identity database has a AspNetUsers table with the following columns that would be of interest for this integration:

  • Id (PK, nvarchar, not null)
  • Email (nvarchar, null)
  • PasswordHash (nvarchar, null)
  • SecurityStamp (nvarchar, null)
  • UserName (nvarchar, not null)

Just to be clear, I'm asking about how to setup the Custom Database scripts within the Auth0 configurations written in JavaScript; not the ASP.NET web app code. The web app is pretty straight forward according to the docs to get setup. It's just the Auth0 built-in custom database templates don't include an example to use for the ASP.NET Core Identity database schema and password hashing.

Any help would be greatly appreciated!


Solution

  • You need to figure out the hashing algorithm used and modify the scripts they have in the templates, using the Auth0 documentation too, to get it correct. You can find code for the algorithm in the aspnet-identity-pw project.

    Here's a sample Login database action script for Auth0 that works with an ASP.NET Core Identity 2.0 database stored in Azure SQL Database:

    function login (username, password, callback) {
    
      var Connection = require('[email protected]').Connection;
      var Request = require('[email protected]').Request;
      var TYPES = require('[email protected]').TYPES;
    
      var connection = new Connection({
        userName:  configuration.db_username + '@' + configuration.db_server,
        password:  configuration.db_password,
        server:    configuration.db_server, //'dbserver.database.windows.net',
        options: {
          database:  configuration.db_database,
          encrypt: true
        }
      });
    
      connection.on('debug', function(text) {
        // if you have connection issues, uncomment this to get more detailed info
        //console.log(text);
      }).on('errorMessage', function(text) {
        // this will show any errors when connecting to the SQL database or with the SQL statements
        //console.log(JSON.stringify(text));
      });
    
      connection.on('connect', function (err) {
        if (err) {
          console.log('error: ' + JSON.stringify(err));
          return callback(err);
        }
    
        getMembershipUser(username, function(err, user) {
          if (err) {
            return callback(err); // this will return a 500
          }
          if (!user.profile) {
            return callback(); // this will return a 401
          }
    
          validatePassword(password, user.password.hash, function(err, isValid) {
            if (!isValid) {
              return callback(); // unauthorized
            }
    
            callback(null, user.profile);
          });
    
        });
      });
    
    
      // Membership Provider implementation used with ASP.NET Core Identity database
    
      /**
       * getMembershipUser
       *
       * This function gets a username or email and returns a user info, password hashes and salt
       *
       * @usernameOrEamil   {[string]}    the username or email, the method will do a query
       *                                  on both with an OR
       * @callback          {[Function]}  first argument will be the Error if any, and second
       *                                  argument will be a user object
       */
      function getMembershipUser(usernameOrEmail, callback) {
        var user = {};
        var query =
          'SELECT Id, UserName, Email, PasswordHash, SecurityStamp from AspNetUsers ' +
          'WHERE UserName = @UserName';
    
        var getMembershipQuery = new Request(query);
    
        getMembershipQuery.addParameter('UserName', TYPES.VarChar, usernameOrEmail);
    
        getMembershipQuery.on('row', function (fields) {
          user.profile = {};
          user.password = {};
          for(var f in fields) {
            var item = fields[f];
            if (item.metadata.colName === 'Id') {
              user.profile.user_id = item.value;
            } else if (item.metadata.colName === 'UserName') {
              user.profile.nickname = item.value;
            } else if (item.metadata.colName ==='Email') {
              user.profile.email = item.value;
            } else if (item.metadata.colName ==='PasswordHash') {
              user.password.hash = item.value;
            }
          }
    
          //console.log('User: ' + JSON.stringify(user));
          callback(null, user);
        });
    
        connection.execSql(getMembershipQuery);
      }
    
      /**
       * validatePassword
       *
       * This function gets the password entered by the user, and the original password
       * hash and salt from database and performs an HMAC SHA256 hash.
       *
       * @password      {[string]}      the password entered by the user
       * @originalHash  {[string]}      the original password hashed from the database
       *                                (including the salt).
       * @return        {[bool]}        true if password validates
       */
      function validatePassword(password, originalHash, callback) {
        aspnet_identity_pw.validatePassword(password, originalHash, function(result, isValid) {
          console.log('Is Password Valid: ' + isValid);
    
          callback(null, isValid);
        });
      }
    
      var aspnet_identity_pw = {
        validatePassword: function(password, hashedPassword, callback) {
          // Original Source:
          //   https://github.com/Syncbak-Git/aspnet-identity-pw/blob/master/lib/aspnet-identity-pw.js
          //   https://www.npmjs.com/package/aspnet-identity-pw
          //   There were some slight modifications to make it run well in Auth0
    
          var done = false;
          var error = null;
          var result = null;
    
          if(!hashedPassword) {
    
              if(callback) {
                  callback(null, false);
              }
    
              return false;
          }
    
          if(!password) {
    
              error = new Error("Password is required.");
    
              if(callback) {
                  callback(error);
                  return;
              }
    
              throw error;
          }
    
          var src = new Buffer(hashedPassword, 'base64');
    
          if(src.length !== 49 || src[0] !== 0) {
              return false;
          }
    
          var salt = new Buffer(16);
          src.copy(salt, 0, 1, 17);
    
          var bytes = new Buffer(32);
          src.copy(bytes, 0, 17, 49);
    
          var hashed = crypto.pbkdf2Sync(password, salt, 1000, 32, 'sha1');
          result = true;
    
              for(var i = 0; i < 32; i++) {
                  if(bytes[i] !== hashed[i]) {
                      result = false;
                      break;
                  }
              }
    
              done = true;
    
              if(callback) {
                  callback(null, result);
              }
    
          if(!callback) {
            throw 'callback required!';
          }
    
          }
      };
    
      }
    

    This took what seemed like forever to get fully figured out. Especially to get the password hashing algorithm coded, until stumbling upon the js project listed with the code for it.

    Hope this helps others!