ログインスクリプトのテンプレート
ログインスクリプトは、ユーザーの認証が必要になるたびに実行される関数を実装します。この関数の名前は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_metadata
、app_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でログインのたびに外部データベースから返された値でname
、nickname
、given_name
、family_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?