Modèles de script de connexion

Le script de connexion met en œuvre la fonction exécutée chaque fois qu’un utilisateur doit s’authentifier. Il est recommandé de nommer cette fonction login.

Ce script est nécessaire à la fois pour l’authentification traditionnelle et pour la migration automatique. Si la migration automatique est configurée pour la connexion, le processus de migration sera déclenché après la première connexion réussie de l’utilisateur.

Fonction de connexion

La fonction connexion doit :

  • Envoyer les identifiants utilisateur fournis à l’API de la base de données externe.

  • Renvoyer les données de profil de l’utilisateur si l’authentification est réussie.

  • Renvoyer une erreur si l’authentification échoue.

Définition

La fonction login accepte trois paramètres et renvoie une fonction callback :

login(userNameOrEmail, password, callback): function

Was this helpful?

/

Paramètre Type Description
userNameOrEmail Chaîne Nom d’utilisateur ou courriel de l’utilisateur.
password Chaîne Mot de passe de l’utilisateur en toutes lettres.
callback Fonction Pour transférer les données d’erreur ou de profil dans le pipeline.

Exemple

Voici un exemple fictif en JavaScript de la manière dont vous pourriez implémenter la fonction login. Pour des exemples spécifiques à un langage, consultez Exemples de scripts spécifiques à un langage.

function login(userNameOrEmail, password, callback) {
  // Send credentials to external database API
  let hashedPassword = hash(password);

  let options = {
    url: "https://example.com/api/authenticate",
    body: {
      email: userNameOrEmail,
      password: hashedPassword
    }
  };

  send(options, (err, profileData) => {
    // Return error in callback if authentication is unsuccessful
    if (err) {
      return callback(new WrongUsernameOrPasswordError(userNameOrEmail, "My custom error message."));
    } else {
      // Return profile data in callback if authentication is successful
      let profile = {
        username: profileData.username,
        email: profileData.emailAddress,
        user_id: profileData.userId
      };

      return callback(null, profile);
    }
  });
}

Was this helpful?

/

Chiffrement

Chiffrez la valeur password à l’aide d’une bibliothèque de chiffrement de hachage cryptographique telle que bcrypt afin d’éviter toute fuite de données potentielle.

Exemple

bcrypt.hash(password, 10, function (err, hash) {
    if (err) {
        return callback(err);
    } else {
        // Return hashed password
    }
});

Was this helpful?

/

Fonction de rappel

La fonction callback (rappel) est utilisée pour transmettre les données du profil utilisateur ou les données d’erreur à travers le pipeline.

Définition

La fonction callback accepte jusqu’à deux paramètres et renvoie une fonction :

callback(error[, profile]): function

Was this helpful?

/

Paramètre Type Requis Description
error Objet Requis Contient des données d’erreur.
profile Objet Facultatif Contient les données du profil de l’utilisateur.

Retourner le profil utilisateur

Si l’utilisateur s’authentifie avec succès, les données de son profil doivent être renvoyées dans l’objet profile sous forme normalisée. Outre les champs standard, vous pouvez inclure les champs user_metadata, app_metadata et mfa_factors.

Exemple

return callback(null, {
    username: "username",
    user_id: "my-custom-db|username@domain.com",
    email: "username@domain.com",
    email_verified: false,
    user_metadata: {
        language: "en"
    },
    app_metadata: {
        plan: "full"
    },
    mfa_factors: [
      {
        phone: {
          value: "+15551234567"
        }
      },
    ]
});

Was this helpful?

/

Renvoyer une erreur

En cas d’erreur, le paramètre error doit contenir des informations pertinentes sur le problème survenu.

Objet de type WrongUsernameOrPasswordError

L’objet de type erreur personnalisé WrongUsernameOrPasswordError vous permet de transmettre des données qui seront affichées dans vos Journaux du locataire.

Constructeur

Le constructeur WrongUsernameOrPasswordError accepte jusqu’à deux paramètres :

new WrongUsernameOrPasswordError(userNameOrEmail[, message]): WrongUsernameOrPasswordError

Was this helpful?

/

Paramètre Type Requis Description
userNameOrEmail Chaîne Requis Contient le nom d’utilisateur ou l’adresse de courriel de l’utilisateur, ou une valeur null.
message Chaîne Facultatif Contient des informations sur l’erreur.

Renvoyer l’erreur avec le nom d’utilisateur ou l’adresse courriel

Si vous renvoyez une erreur avec une valeur pour le champ userNameOrEmail, Auth0 enregistrera un événement de journal du locataire fp.

Exemple

return callback(new WrongUsernameOrPasswordError(userNameOrEmail, "My custom error message"));

Was this helpful?

/

Champ d’événement de journal de locataire Valeur
Code fp
Événement Échec de la connexion (Mot de passe incorrect)
Description My custom error message

Retour d’erreur sans nom d’utilisateur ou courriel

Si vous renvoyez une erreur avec une valeur null pour le champ userNameOrEmail, Auth0 enregistrera un événement de journal du locataire fu.

Exemple

return callback(new WrongUsernameOrPasswordError(null, "My custom error message"));

Was this helpful?

/

Champ d’événement du journal des locataires Valeur
Code fu
Événement Échec de connexion (Email/nom d’utilisateur invalide)
Description My custom error message

Synchroniser les attributs du profil utilisateur à chaque connexion

Activez le paramètre Synchroniser les attributs du profil utilisateur à chaque connexion si vous souhaitez qu’Auth0 mette à jour les champs name, nickname, given_name, family_name et/ou picture avec les valeurs renvoyées par la base de données externe à chaque connexion.

Si vous n’activez pas ce paramètre, les valeurs renvoyées par la base de données externe lors de la première connexion de l’utilisateur seront conservées lors des connexions suivantes, même si elles ont été modifiées dans la base de données externe.

Exemples de scripts spécifiques à un langage

Auth0 fournit des exemples de scripts à utiliser avec les langages/technologies ci-dessous :

JavaScript

function login(email, password, callback) {
  // This script should authenticate a user against the credentials stored in
  // your database.
  // It is executed when a user attempts to log in or immediately after signing
  // up (as a verification that the user was successfully signed up).
  //
  // Everything returned by this script will be set as part of the user profile
  // and will be visible by any of the tenant admins. Avoid adding attributes
  // with values such as passwords, keys, secrets, etc.
  //
  // The `password` parameter of this function is in plain text. It must be
  // hashed/salted to match whatever is stored in your database. For example:
  //
  //     var bcrypt = require('bcrypt@0.8.5');
  //     bcrypt.compare(password, dbPasswordHash, function(err, res)) { ... }
  //
  // There are three ways this script can finish:
  // 1. The user's credentials are valid. The returned user profile should be in
  // the following format: https://auth0.com/docs/users/normalized/auth0/normalized-user-profile-schema
  //     var profile = {
  //       user_id: ..., // user_id is mandatory
  //       email: ...,
  //       [...]
  //     };
  //     callback(null, profile);
  // 2. The user's credentials are invalid
  //     callback(new WrongUsernameOrPasswordError(email, "my error message"));
  // 3. Something went wrong while trying to reach your database
  //     callback(new Error("my error message"));
  //
  // A list of Node.js modules which can be referenced is available here:
  //
  //    https://tehsis.github.io/webtaskio-canirequire/
  const msg = 'Please implement the Login script for this database connection ' +
    'at https://manage.auth0.com/#/connections/database';
  return callback(new Error(msg));
}

Was this helpful?

/

Fournisseur d’adhésion ASP.NET (MVC3 - Fournisseurs universels)

function login(email, password, callback) {
  const crypto = require('crypto');
  const sqlserver = require('tedious@11.0.3');
  const Connection = sqlserver.Connection;
  const Request = sqlserver.Request;
  const TYPES = sqlserver.TYPES;
  const connection = new Connection({
    userName: 'the username',
    password: 'the password',
    server: 'the server',
    options: {
      database: 'the db name',
      encrypt: true // for Windows Azure
    }
  });
  /**
   * hashPassword
   *
   * This function creates a hashed version of the password to store in the database.
   *
   * @password  {[string]}      the password entered by the user
   * @return    {[string]}      the hashed password
   */
  function hashPassword(password, salt) {
    // the default implementation uses HMACSHA256 and since Key length is 64
    // and default salt is 16 bytes, Membership will fill the buffer repeating the salt
    const key = Buffer.concat([salt, salt, salt, salt]);
    const hmac = crypto.createHmac('sha256', key);
    hmac.update(Buffer.from(password, 'ucs2'));
    return hmac.digest('base64');
  }
  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) return callback(err);
    getMembershipUser(email, function(err, user) {
      if (err || !user || !user.profile || !user.password) return callback(err || new WrongUsernameOrPasswordError(email));
      const salt = Buffer.from(user.password.salt, 'base64');
      if (hashPassword(password, salt).toString('base64') !== user.password.password) {
        return callback(new WrongUsernameOrPasswordError(email));
      }
      callback(null, user.profile);
    });
  });

  // Membership Provider implementation used on Microsoft.AspNet.Providers NuGet
  /**
   * getMembershipUser
   *
   * This function gets a username or email and returns a the user membership provider
   * info, password hashes and salt
   *
   * @usernameOrEmail  {[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, done) {
    var user = null;
    const query =
      'SELECT Memberships.UserId, Email, Users.UserName, Password ' +
      'FROM Memberships INNER JOIN Users ' +
      'ON Users.UserId = Memberships.UserId ' +
      'WHERE Memberships.Email = @Username OR Users.UserName = @Username';
    const getMembershipQuery = new Request(query, function(err, rowCount) {
      if (err || rowCount < 1) return done(err);
      done(err, user);
    });
    getMembershipQuery.addParameter('Username', TYPES.VarChar, usernameOrEmail);
    getMembershipQuery.on('row', function(fields) {
      user = {
        profile: {
          user_id: fields.UserId.value,
          nickname: fields.UserName.value,
          email: fields.Email.value,
        },
        password: {
          password: fields.Password.value,
          salt: fields.PasswordSalt.value
        }
      };
    });
    connection.execSql(getMembershipQuery);
  }
}

Was this helpful?

/

Fournisseur d’adhésion ASP.NET (MVC4 - Abonnement simple)

function login(email, password, callback) {
  const crypto = require('crypto');
  const sqlserver = require('tedious@11.0.3');
  const Connection = sqlserver.Connection;
  const Request = sqlserver.Request;
  const TYPES = sqlserver.TYPES;
  const connection = new Connection({
    userName: 'the username',
    password: 'the password',
    server: 'the server',
    options: {
      database: 'the db name',
      encrypt: true // for Windows Azure
    }
  });
  function fixedTimeComparison(a, b) {
    var mismatch = (a.length === b.length ? 0 : 1);
    if (mismatch) {
      b = a;
    }
    for (var i = 0, il = a.length; i < il; ++i) {
      const ac = a.charCodeAt(i);
      const bc = b.charCodeAt(i);
      mismatch += (ac === bc ? 0 : 1);
    }
    return (mismatch === 0);
  }

  /**
   * 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) {
    const iterations = 1000;
    const hashBytes = Buffer.from(originalHash, 'base64');
    const salt = hashBytes.slice(1, 17);
    const hash = hashBytes.slice(17, 49);
    crypto.pbkdf2(password, salt, iterations, hash.length, 'sha1', function(err, hashed) {
      if (err) return callback(err);
      const hashedBase64 = Buffer.from(hashed, 'binary').toString('base64');
      const isValid = fixedTimeComparison(hash.toString('base64'), hashedBase64);
      return callback(null, isValid);
    });
  }

  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) return callback(err);
    getMembershipUser(email, function(err, user) {
      if (err || !user || !user.profile) return callback(err || new WrongUsernameOrPasswordError(email));
      validatePassword(password, user.password, function(err, isValid) {
        if (err || !isValid) return callback(err || new WrongUsernameOrPasswordError(email));
        callback(null, user.profile);
      });
    });
  });

  // Membership Provider implementation used on Microsoft.AspNet.Providers NuGet
  /**
   * 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, done) {
    var user = null;
    const query =
      'SELECT webpages_Membership.UserId, UserName, UserProfile.UserName, Password from webpages_Membership ' +
      'INNER JOIN UserProfile ON UserProfile.UserId = webpages_Membership.UserId ' +
      'WHERE UserProfile.UserName = @Username';
    const getMembershipQuery = new Request(query, function(err, rowCount) {
      if (err || rowCount < 1) return done(err);
      done(err, user);
    });
    getMembershipQuery.addParameter('Username', TYPES.VarChar, usernameOrEmail);
    getMembershipQuery.on('row', function(fields) {
      user = {
        profile: {
          user_id: fields.UserId.value,
          nickname: fields.UserName.value,
          email: fields.UserName.value,
        },
        password: fields.Password.value
      };
    });
    connection.execSql(getMembershipQuery);
  }
}

Was this helpful?

/

MongoDB

function login(email, password, callback) {
  const bcrypt = require('bcrypt');
  const MongoClient = require('mongodb@3.1.4').MongoClient;
  const client = new MongoClient('mongodb://user:pass@mymongoserver.com');
  client.connect(function (err) {
    if (err) return callback(err);
    const db = client.db('db-name');
    const users = db.collection('users');
    users.findOne({ email: email }, function (err, user) {
      if (err || !user) {
        client.close();
        return callback(err || new WrongUsernameOrPasswordError(email));
      }
      bcrypt.compare(password, user.password, function (err, isValid) {
        client.close();
        if (err || !isValid) return callback(err || new WrongUsernameOrPasswordError(email));
        return callback(null, {
            user_id: user._id.toString(),
            nickname: user.nickname,
            email: user.email
          });
      });
    });
  });
}

Was this helpful?

/

MySQL

function login(email, password, callback) {
  const mysql = require('mysql');
  const bcrypt = require('bcrypt');
  const connection = mysql({
    host: 'localhost',
    user: 'me',
    password: 'secret',
    database: 'mydb'
  });
  connection.connect();
  const query = 'SELECT id, nickname, email, password FROM users WHERE email = ?';
  connection.query(query, [ email ], function(err, results) {
    if (err) return callback(err);
    if (results.length === 0) return callback(new WrongUsernameOrPasswordError(email));
    const user = results[0];
    bcrypt.compare(password, user.password, function(err, isValid) {
      if (err || !isValid) return callback(err || new WrongUsernameOrPasswordError(email));
      callback(null, {
        user_id: user.id.toString(),
        nickname: user.nickname,
        email: user.email
      });
    });
  });
}

Was this helpful?

/

PostgreSQL

function login(email, password, callback) {
  //this example uses the "pg" library
  //more info here: https://github.com/brianc/node-postgres
  const bcrypt = require('bcrypt');
  const postgres = require('pg');
  const conString = 'postgres://user:pass@localhost/mydb';
  postgres.connect(conString, function (err, client, done) {
    if (err) return callback(err);
    const query = 'SELECT id, nickname, email, password FROM users WHERE email = $1';
    client.query(query, [email], function (err, result) {
      // NOTE: always call `done()` here to close
      // the connection to the database
      done();
      if (err || result.rows.length === 0) return callback(err || new WrongUsernameOrPasswordError(email));
      const user = result.rows[0];
      bcrypt.compare(password, user.password, function (err, isValid) {
        if (err || !isValid) return callback(err || new WrongUsernameOrPasswordError(email));
        return callback(null, {
          user_id: user.id,
          nickname: user.nickname,
          email: user.email
        });
      });
    });
  });
}

Was this helpful?

/

SQL Server

function login(email, password, callback) {
  //this example uses the "tedious" library
  //more info here: http://pekim.github.io/tedious/index.html
  const bcrypt = require('bcrypt');
  const sqlserver = require('tedious@11.0.3');
  const Connection = sqlserver.Connection;
  const Request = sqlserver.Request;
  const TYPES = sqlserver.TYPES;
  const connection = new Connection({
    userName:  'test',
    password:  'test',
    server:    'localhost',
    options:  {
      database: 'mydb',
      rowCollectionOnRequestCompletion: true
    }
  });
  const query = 'SELECT Id, Nickname, Email, Password FROM dbo.Users WHERE Email = @Email';
  connection.on('debug', function (text) {
    console.log(text);
  }).on('errorMessage', function (text) {
    console.log(JSON.stringify(text, null, 2));
  }).on('infoMessage', function (text) {
    console.log(JSON.stringify(text, null, 2));
  });
  connection.on('connect', function (err) {
    if (err) return callback(err);
    const request = new Request(query, function (err, rowCount, rows) {
      if (err || rowCount < 1) return callback(err || new WrongUsernameOrPasswordError(email));
      bcrypt.compare(password, rows[0][3].value, function (err, isValid) {
        if (err || !isValid) return callback(err || new WrongUsernameOrPasswordError(email));
        callback(null, {
          user_id: rows[0][0].value,
          nickname: rows[0][1].value,
          email: rows[0][2].value
        });
      });
    });
    request.addParameter('Email', TYPES.VarChar, email);
    connection.execSql(request);
  });
}

Was this helpful?

/

Base de données SQL de Windows Azure

function login(email, password, callback) {
  //this example uses the "tedious" library
  //more info here: http://pekim.github.io/tedious/index.html
  var Connection = require('tedious@11.0.3').Connection;
  var Request = require('tedious@11.0.3').Request;
  var TYPES = require('tedious@11.0.3').TYPES;
  var bcrypt = require('bcrypt');
  var connection = new Connection({
    userName: 'your-user@your-server-id.database.windows.net',
    password: 'the-password',
    server: 'your-server-id.database.windows.net',
    options: {
      database: 'mydb',
      encrypt: true,
      rowCollectionOnRequestCompletion: true
    }
  });
  var query = "SELECT Id, Email, Password " +
    "FROM dbo.Users WHERE Email = @Email";
  connection.on('debug', function (text) {
    // Uncomment next line in order to enable debugging messages
    // console.log(text);
  }).on('errorMessage', function (text) {
    console.log(JSON.stringify(text, null, 2));
    return callback(text);
  }).on('infoMessage', function (text) {
    // Uncomment next line in order to enable information messages
    // console.log(JSON.stringify(text, null, 2));
  });
  connection.on('connect', function (err) {
    if (err) { return callback(err); }
    var request = new Request(query, function (err, rowCount, rows) {
      if (err) {
        callback(new Error(err));
      } else if (rowCount < 1) {
        callback(new WrongUsernameOrPasswordError(email));
      } else {
        bcrypt.compare(password, rows[0][2].value, function (err, isValid) {
          if (err) { callback(new Error(err)); }
          else if (!isValid) { callback(new WrongUsernameOrPasswordError(email)); }
          else {
            callback(null, {
              user_id: rows[0][0].value,
              email: rows[0][1].value
            });
          }
        });
      }
    });
    request.addParameter('Email', TYPES.VarChar, email);
    connection.execSql(request);
  });
}

Was this helpful?

/

Axios

async function loginAsync(email, password, callback) {
  //should be updated as new versions of axios are made available (https://auth0-extensions.github.io/canirequire/#axios)
  const axios = require("axios@0.22.0");

  let response;

  try {
    response = await axios.post(
      //store API url in connection settings to better support SDLC environments
      configuration.baseAPIUrl + "/login",
      //user credentials passed as request body
      {
        email: email,
        password: password,
      },
      {
        timeout: 10000, //end call gracefully if request times out so script can do necessary callback
        headers: {
          //securing api call with apiKey stored in connection settings.
          //quick and easy approach however using M2M tokens is more secure as
          // a secret must not be shared between client and API.
          "x-api-key": configuration.apiKey,
        },
      }
    );
  } catch (e) {
    if (e.response.status === 404) {
      //assuming api returns 404 when email/username/password invalid
      return callback(
        new WrongUsernameOrPasswordError(email, "Invalid credentials provided.")
      );
    }
    //callback for any other error type
    return callback(new Error(e.message));
  }

  try {
    let user = response.data;

    //if using multiple custom db connections in your tenant prefix the
    //user_id with a connection specific key ex: "connName|" + user.user_id
    //this ensures unique user ids across all db connections
    return callback(null, {
      user_id: user.user_id,
      email: user.email,
    });
  } catch (e) {
    return callback(new Error(e.message));
  }
}

Was this helpful?

/

Stormpath

function login(username, password, callback) {
  // Replace the {yourStormpathClientId} with your Stormpath ID
  var url = 'https://api.stormpath.com/v1/applications/{yourStormpathClientId}/loginAttempts';
  // Add your Stormpath API Client ID and Secret
  var apiCredentials = {
    user : '{yourStormpathApiId}',
    password: '{yourStormpathApiSecret}'
  };
  // Stormpath requires the user credentials be passed in as a base64 encoded message
  var credentials = Buffer.from(username + ':' + password).toString('base64');
  // Make a POST request to authenticate a user
  request({
    url: url,
    method: 'POST',
    auth: apiCredentials,
    json: {
      type: 'basic',
      // Passing in the base64 encoded credentials
      value: credentials
    }
  }, function (error, response, body) {
    // If response is successful we'll continue
    if (response.statusCode !== 200) return callback();
    // A successful response will return a URL to get the user information
    var accountUrl = body.account.href;
    // Make a second request to get the user info.
    request({
      url: accountUrl,
      auth: apiCredentials,
      json: true
    }, function (errorUserInfo, responseUserInfo, bodyUserInfo) {
      // If we get a successful response, we'll process it
      if (responseUserInfo.statusCode !== 200) return callback();
      // To get the user identifier, we'll strip out the Stormpath API
      var id = bodyUserInfo.href.replace('https://api.stormpath.com/v1/accounts/', '');
      // Finally, we'll set the data we want to store in Auth0 and migrate the user
      return callback(null, {
        user_id : id,
        username: bodyUserInfo.username,
        email: bodyUserInfo.email,
        // We set the users email_verified to true as we assume if they were a valid
        // user in Stormpath, they have already verified their email
        // If this field is not set, the user will get an email asking them to verify
        // their account. You can decide how to handle this for your use case
        email_verified: true
        // Add any additional fields you would like to carry over from Stormpath
      });
    });
  });
}

Was this helpful?

/

En savoir plus