Login Script Templates
The Login script implements the function executed each time a user is required to authenticate. We recommend naming this function login
.
This script is required for both legacy authentication and for automatic migration. If automatic migration is configured for the connection, the migration process will be triggered after the first time the user logs in successfully.
Login function
The login
function should:
Send the provided user credentials to the external database’s API.
Return the profile data of the user if authentication is successful.
Return an error if authentication is unsuccessful.
Definition
The login
function accepts three parameters and returns a callback
function:
login(userNameOrEmail, password, callback): function
Was this helpful?
Parameter | Type | Description |
---|---|---|
userNameOrEmail |
String | The user's username or email. |
password |
String | The user's password in plain text. |
callback |
Function | Used to pass error or profile data through the pipeline. |
Example
This is a pseudo-JavaScript example of how you could implement the login
function. For language-specific examples, read Language-specific script examples.
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
Encrypt the password
value using a cryptographic hash encryption library such as bcrypt
to prevent any potential data leak.
Example
bcrypt.hash(password, 10, function (err, hash) {
if (err) {
return callback(err);
} else {
// Return hashed password
}
});
Was this helpful?
Callback function
The callback
function is used to pass user profile data or error data through the pipeline.
Definition
The callback
function accepts up to two parameters and returns a function:
callback(error[, profile]): function
Was this helpful?
Parameter | Type | Required | Description |
---|---|---|---|
error |
Object | Required | Contains error data. |
profile |
Object | Optional | Contains the user's profile data. |
Return the user profile
If the user authenticates successfully, their profile data must be returned in the profile
object in normalized form. In addition to the standard fields, you can include the user_metadata
, app_metadata
, and mfa_factors
fields.
Example
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?
Return an error
If an error occurs, the error
parameter should contain relevant information about what went wrong.
WrongUsernameOrPasswordError type object
The WrongUsernameOrPasswordError
custom error type object allows you to pass data that will be displayed in your Tenant Logs.
Constructor
The WrongUsernameOrPasswordError
constructor accepts up to two parameters:
new WrongUsernameOrPasswordError(userNameOrEmail[, message]): WrongUsernameOrPasswordError
Was this helpful?
Parameter | Type | Required | Description |
---|---|---|---|
userNameOrEmail |
String | Required | Contains the user's username or email, or a null value. |
message |
String | Optional | Contains information about the error. |
Return error with username or email
If you return an error with a value for the userNameOrEmail
field, Auth0 will record an fp
tenant log event.
Example
return callback(new WrongUsernameOrPasswordError(userNameOrEmail, "My custom error message"));
Was this helpful?
Tenant Log Event Field | Value |
---|---|
Code | fp |
Event | Failed Login (Incorrect Password) |
Description | My custom error message |
Return error without username or email
If you return an error with a null
value for the userNameOrEmail
field, Auth0 will record an fu
tenant log event.
Example
return callback(new WrongUsernameOrPasswordError(null, "My custom error message"));
Was this helpful?
Tenant Log Event Field | Value |
---|---|
Code | fu |
Event | Failed Login (Invalid Email/Username) |
Description | My custom error message |
Sync user profile attributes at each login
Enable the Sync user profile attributes at each login setting if you want Auth0 to update the name
, nickname
, given_name
, family_name
, and/or picture
fields with the values returned from the external database on each login.
If you do not enable this setting, the values returned from the external database the first time the user logs in will persist on subsequent logins, even if they've changed in the external database.
Language-specific script examples
Auth0 provides sample scripts for use with the following languages/technologies:
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?