# Login

The script will be executed when the user tries to log in. If the user is not synchronized to the Authing database, the user account password will be checked according to the script you filled in. This script is required for both lazy migration of users and full use of custom database schema.

# Function definition

The login function is defined as follows:

async function login(query, password, context) {
  // The first argument query contains following contents:
  // query.email
  // query.username
  // query.phone

  // The second argument password is user's password in plain text format.

  // The Second argument context contains information about the authentication context.
  // see http://core.authing.cn/connections/custom-db/config-custom-db-connection.html for more information.

  // This script should retrieve a user profile from your existing database,
  // without authenticating the user.

  // It is used to check if a user exists before executing flows that do not
  // require authentication (signup and password reset).
  //
  // There are three ways this script can finish:
  // 1. A user was successfully found, and password is valid, The profile should be in the following
  // format: https://docs.authing.co/user/profile.html.
  // return profile
  // 2. A user was not found
  // throw new Error('User not exists!');
  // 3. A user was found, but password is not valid
  // throw new Error('Password is not valid!');
  // 4. Something went wrong while trying to reach your database:
  // throw new Error("my error message")

  const msg ='Please implement the Login script for this database connection';
  throw new Error(msg);
}
Parameters Type nullable Description
query object false query conditions
query.email string ture Email. This parameter is not empty when the user logs in with the mailbox.
query.phone string true Mobile phone number. This parameter is not empty when the user logs in with the mobile phone number.
query.username string true User name. This parameter is not empty when the user logs in with the user name.
password string false Clear text password. It is strongly recommended to use bcrypt to encrypt user passwords, see below for details.
context object true request context context

The context contains the following information:

Property name Type Description
userPoolId string User Pool ID
userPoolName string User pool name
userPoolMetadata object User pool configuration information
appId string ID of the current user, **You can distinguish the source of the application requested by the user through appId. **
appName string The name of the current application
appMetadata object Current application configuration information
application string User Pool ID
request object The detailed information of the current request, including:
ip: client IP
geo: client geographic location resolved by IP
body: request body

# Return data convention

# The user exists and the password is correct

When the user exists and the password is correct, you need to return the user information of the user to Authing. For the detailed format of the user information, please see: User Profile Field. Example:

async function login(query, password, context) {
  // Implement your logic here
  return {
    id: 1, // must not empty
    email: "test@example.com",
    emailVerified: true,
    nickname: "Nick",
    photo: ""
  }
}

# User does not exist

When the user does not exist, you need to directly throw an error (the error message can be freely defined), for example:

async function login(query, password, context) {
  // Implement your logic here
  throw new Error('User not exists');
}

# The user exists but the password is incorrect

When the user exists but the password is incorrect, you need to directly throw an error (the error message can be freely defined), for example:

async function login(query, password, context) {
  // Implement your logic here
  throw new Error('User not exists');
}

# Other exception errors

When encountering other abnormal errors, you can catch the error and return a more friendly error prompt, for example:

async function login(query, password, context) {
  try {
    // Implement your logic here
  } catch (error) {
    throw new Error('Something went wrong ...')
  }
}

# Best Practices

# Provide friendly error tips

When encountering an unknown error, we recommend throwing a standard Error object. Authing will catch this error and return it to the end user. For example: throw new Error("My nice error message"), you can see the error log in the log history of the custom database.

# Use bcrypt to encrypt passwords

We recommend using bcrypt to encrypt and save user data, such as:

const bcrypt = require('bcrypt');
const hashedPassword = await bcrypt.hash(
  'passw0rd',
  await bcrypt.genSalt(10),
);

Verify that the password is correct:

const bcrypt = require('bcrypt');
const valid = await bcrypt.compare('passw0rd', user.password);

# Disconnect the database connection at the end of the function

Please remember to close the connection to the database when the script is executed, such as calling client.end(). For example, it can be executed in try/finallly to ensureIt will always be executed:

try {
  const result = await client.query("YOUR QUERY");
} finally {
  // NOTE: always call `client.end()` here to close the connection to the database
  client.end();
}

# Example function

Taking the postgres database as an example, the following points are explained:

-You can get the database connection string through env.DB_CONNECTION_URI to create a database connection. -Dynamically create query statements based on the query conditions passed in query (query.email, query.username, query.phone may be empty, but not at the same time). -If the user does not exist, throw an exception directly and the error message is User not exists!. -If the user exists but the password is incorrect, an exception will be thrown directly and the error message is Password is not valid!. -Finally, the user information in the specified format is returned. For the detailed format of the user information, please see: User Profile Field. -Call client.end() in try/finally to disconnect the database.

async function login(query, password, context) {
  // The first argument query contains following contents:
  // query.email
  // query.username
  // query.phone
  const {email, username, phone} = query;

  // The second argument password is user's password in plain text format.

  // The last argument context contains information about the authentication context.
  // see http://core.authing.cn/connections/custom-db/config-custom-db-connection.html for more information.

  // This example uses the "pg" library
  // more info here: https://github.com/brianc/node-postgres
  const {Client} = require('pg');

  const client = new Client({
    connectionString: env.DB_CONNECTION_URI,
  });

  // Or you can:
  // const client = new Client({
  // host: env.DB_HOST,
  // port: env.DB_PORT,
  // user: env.DB_USERNAME,
  // password: env.DB_PASSWORD,
  // database: env.DB_DATABASE,
  // });

  await client.connect();

  // Use bcrypt to validate password
  // more info here: https://github.com/kelektiv/node.bcrypt.js
  const bcrypt = require('bcrypt');

  // Build query parameters
  const queries = [];
  const parameters = [];
  let index = 1;
  if (email) {
    queries.push(`email = $${index}`);
    parameters.push(email);
    index += 1;
  }
  if (phone) {
    queries.push(`phone = $${index}`);
    parameters.push(phone);
    index += 1;
  }
  if (username) {
    queries.push(`username = $${index}`);
    parameters.push(username);
    index += 1;
  }

  const QUERY = `SELECT * FROM users WHERE ${queries.join(' OR')}`;

  try {
    const result = await client.query(QUERY, parameters);
    if (result.rows.length === 0) {
      throw new Error('User not exists!');
    }
    const user = result.rows[0];

    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      throw new Error('Password is not valid!');
    }

    return {
      id: user.id,
      email: user.email,
      phone: user.phone,
      username: user.username,
      photo: user.photo,
      nickname: user.nickname,
      token: user.token,
      emailVerified: user.email_verified,
      phoneVerified: user.phone_verified,
      loginsCount: user.logins_count,
      lastIp: user.last_ip,
      gender: user.gender,
      address: user.address,
      company: user.company,
      birthdate: user.birthdate,
      website: user.website,
    };
  } catch (error) {
    throw new Error(`Execute query failed: ${error.message}`);
  } finally {
    // NOTE: always call `client.end()` here to close the connection to the database
    client.end();
  }
}