ログインスクリプトのテンプレート

ログインスクリプトは、ユーザーの認証が必要になるたびに実行される関数を実装します。この関数の名前はloginにすることをお勧めします。

このスクリプトは、レガシー認証と自動移行の両方で必要です。自動移行が接続に対して構成されている場合、移行プロセスはユーザーが初めて正常にログインするときにトリガーされます。

Login関数

login関数は以下を行います。

  • ユーザー資格情報を外部データベースのAPIに送信する。

  • 認証が成功したらユーザーのプロファイルデータを返す。

  • 認証に失敗したらエラーを返す。

定義

login関数は3つのパラメーターを受け取り、callback関数を返します。

login(userNameOrEmail, password, callback): function

Was this helpful?

/

パラメーター タイプ 説明
userNameOrEmail 文字列 ユーザーのユーザー名またはメール。
password 文字列 ユーザーのパスワード(プレーンテキスト形式)。
callback 関数 エラーやプロファイルデータをパイプラインを介して渡すために使用されます。

これは疑似JavaScriptを使った例で、どのようにすればlogin関数を実装できるかがわかります。言語固有の例については、「言語固有のスクリプトの例」をお読みください。

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?

/

Encryption(暗号化)

bcryptのような暗号学的ハッシュの暗号化ライブラリを使ってパスワード値を暗号化し、潜在的なデータ漏洩を防ぎます。

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

Was this helpful?

/

コールバック関数

callback関数は、パイプラインを通してユーザープロファイルデータやエラーデータを渡すのに使用されます。

定義

callback関数は2つまでのパラメーターを受け取り、1つの関数を返します。

callback(error[, profile]): function

Was this helpful?

/

パラメーター 種類 必須 説明
error オブジェクト 必須 エラーデータを含む。
profile オブジェクト 任意 ユーザーのプロファイルデータを含む。

ユーザープロファイルを返す

ユーザーが認証に成功した場合、プロファイルデータは正規化フォームprofileオブジェクトに返される必要があります。標準フィールドのほかにも、user_metadataapp_metadata、およびmfa_factorsフィールドを含めることができます。

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?

/

エラーの場合

エラーが発生した場合には、何が起きたかについての関連情報がerrorパラメーターに含まれるようにします。

WrongUsernameOrPasswordErrorタイプのオブジェクト

WrongUsernameOrPasswordErrorのカスタムエラータイプのオブジェクトを使うと、テナントログで表示されるデータを渡すことができます。

コンストラクター

WrongUsernameOrPasswordErrorコンストラクターは、最大2つのパラメーターを受け取ります。

new WrongUsernameOrPasswordError(userNameOrEmail[, message]): WrongUsernameOrPasswordError

Was this helpful?

/

パラメーター タイプ 必須 説明
userNameOrEmail 文字列 必須 ユーザーのユーザー名またはメール、もしくはnull値が含まれます。
message 文字列 任意 エラーに関する情報が含まれます。

ユーザー名またはメールでエラーを返す

userNameOrEmailフィールドの値でエラーを返すと、Auth0はfpのテナントログイベントを記録します。

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

Was this helpful?

/

テナントログイベントフィールド
Code(コード) fp
Event(イベント) ログイン失敗(パスワードの間違い)
Description(説明) My custom error message

ユーザー名またはメールなしでエラーを返す

userNameOrEmailフィールドのnull値でエラーを返すと、Auth0はfuのテナントログイベントを記録します。

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

Was this helpful?

/

テナントログイベントのフィールド
Code(コード) fu
Event(イベント) 失敗したログイン(無効なメールアドレス/ユーザー名)
Description(説明) My custom error message

[Sync user profile attributes at each login(ログインの度にユーザープロファイル属性を同期する)]

Auth0でログインのたびに外部データベースから返された値でnamenicknamegiven_namefamily_name、およびpictureフィールドを更新したい場合は、[Sync user profile attributes at each login(ログインのたびにユーザープロファイル属性を同期する)]設定を有効にします。

この設定を有効にしないと、ログインのたびに外部データベースから返された値は、外部データベースで変更したとしても、以降のログインでも保持されます。

言語固有のスクリプトの例

Auth0は、以下の言語や技術で使用できるサンプルスクリプトを提供しています。

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?

/

ASP.NET Membership Provider(MVC3 - Universal Providers)

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?

/

ASP.NET Membership Provider (MVC4 - Simple Membership)

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?

/

Windows Azure SQL Database

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?

/

もっと詳しく