Setup Lambda Function

Previously, we created the Lambda function, now we have to handle the operations required in serverless authentication. Follow the steps below:

  • Open a folder in your local computer and look at the directory structure given below.

    Setup Lambda Function

  • Create a package.json file in the root directory with the following content.

    Node

    // package.json file

    {
    “name”: “login-backend”,
    “version”: “1.0.0”,
    “description”: “”,
    “main”: “index.js”,
    “scripts”: {
    “test”: “echo \”Error: no test specified\” && exit 1″
    },
    “author”: “arindam369”,
    “license”: “ISC”,
    “dependencies”: {
    “bcryptjs”: “^2.4.3”,
    “jsonwebtoken”: “^9.0.2”
    }
    }

    • Create index.js file in the root directory with the following content
    • Create a “services” directory and inside that directory create 4 files – register.js, login.js, verify.js, logout.js with the following contents.
    • Create a “utils” directory and inside that directory create 2 files – auth.js, builder.js with the following contents.

    Node

    // index.js file

    const registerService = require(“./services/register”);
    const loginService = require(“./services/login”);
    const verifyService = require(“./services/verify”);
    const logoutService = require(“./services/logout”);
    const builder = require(“./utils/builder”);

    exports.handler = async (event) => {
    if (event.path === “/register” && event.httpMethod === “POST”) { // handling user’s sign up process
    const response = await registerService.register(
    event.name,
    event.username,
    event.password
    );
    return builder.buildResponse(200, response);
    } else if (event.path === “/login” && event.httpMethod === “POST”) { // handling user’s sign in process
    const response = await loginService.login(event.username, event.password);
    return builder.buildResponse(200, response);
    } else if (event.path === “/verify” && event.httpMethod === “POST”) { // handling user’s auth-verification process
    const response = await verifyService.verify(event.username, event.token);
    return builder.buildResponse(200, response);
    } else if (event.path === “/logout” && event.httpMethod === “POST”) { // handling user’s signout process
    const response = await logoutService.logout(event.username, event.token);
    return builder.buildResponse(200, response);
    } else {
    return builder.buildResponse(400, {
    message: `${event.httpMethod} is not allowed in ${event.path} route`,
    });
    }
    };

    Node

    // /services/register.js file

    const builder = require(“../utils/builder”);
    const bcrypt = require(“bcryptjs”);
    const AWS = require(“aws-sdk”);
    const dynamoDB = new AWS.DynamoDB.DocumentClient({
    region: ‘us-east-1’,
    apiVersion: ‘2012-08-10’,
    });
    const SALT_ROUND = 8;

    // register user using name, username, password
    async function register(name, username, password){
    if(!name || !username || !password){
    return builder.buildResponse(400, {message: “Missing required fields”});
    }

    const foundUser = await getUser(username);
    if(foundUser && foundUser.username){
    return builder.buildResponse(400, {message: “User already exists”});
    }

    const hashedPass = bcrypt.hashSync(password.trim(), SALT_ROUND);
    const newUser = {
    name,
    username,
    password: hashedPass
    };
    const saveUserResponse = await saveUser(newUser);
    if(!saveUserResponse) return builder.buildResponse(400, {message: “Server Error: Please try again later”});
    return builder.buildResponse(200, {message: “User registered successfully”});
    }

    // retrieve user data via username from DynamoDB
    const getUser = async (username)=>{
    const params = {
    Key: {
    username: username
    },
    TableName: “login-database”
    }

    return await dynamoDB.get(params).promise().then((response)=>{
    return response.Item;
    }).catch((err)=>{
    return err;
    })
    }

    // save user in DynamoDB
    const saveUser = async (user)=>{
    const params = {
    Item: user,
    TableName: “login-database”
    }
    return await dynamoDB.put(params).promise().then(()=>{
    return true;
    }).catch((err)=>{
    return err;
    })
    }

    module.exports.register = register;

    Node

    // /services/login.js file

    const builder = require(“../utils/builder”);
    const bcrypt = require(“bcryptjs”);
    const auth = require(“../utils/auth”);
    const AWS = require(“aws-sdk”);
    const dynamoDB = new AWS.DynamoDB.DocumentClient({
    region: ‘us-east-1’,
    apiVersion: ‘2012-08-10’,
    });
    const SALT_ROUND = 8;

    async function login(username, password){
    if(!username || !password){
    return builder.buildResponse(400, {message: “Missing required fields”});
    }

    const foundUser = await getUser(username);
    if(!foundUser || !foundUser.username){ // user doesn’t exist in database
    return builder.buildResponse(400, {message: “User doesn’t exist”});
    }
    if(!bcrypt.compareSync(password, foundUser.password)){ // password doesn’t match with the existing password
    return builder.buildResponse(403, {message: “Wrong Password”});
    }

    const token = auth.generateToken(foundUser.username); // generate token encapsulating username
    const tokenArray = foundUser.tokens || [];
    tokenArray.push(token); // store the generated token in the database
    const params = {
    Key: {
    username: username
    },
    UpdateExpression: `set tokens = :value`,
    ExpressionAttributeValues: {
    “:value”: tokenArray
    },
    TableName: “login-database”,
    ReturnValues: “UPDATED_NEW”
    };

    const response = {
    username: foundUser.username,
    name: foundUser.name,
    token: token
    };

    return await dynamoDB.update(params).promise().then(()=>{
    return builder.buildResponse(200, {message: “User logged in successfully”, response});
    }).catch((err)=>{
    return builder.buildResponse(400, {message: err});
    })
    }

    // retrieve user data via username from DynamoDB
    const getUser = async (username)=>{
    const params = {
    Key: {
    username: username
    },
    TableName: “login-database”
    }

    return await dynamoDB.get(params).promise().then((response)=>{
    return response.Item;
    }).catch((err)=>{
    return err;
    })
    }

    module.exports.login = login;

    Node

    // /services/verify.js file

    const auth = require(“../utils/auth”);
    const builder = require(“../utils/builder”);
    const AWS = require(“aws-sdk”);
    const dynamoDB = new AWS.DynamoDB.DocumentClient({
    region: ‘us-east-1’,
    apiVersion: ‘2012-08-10’,
    });

    async function verify(username, token){
    if(!username || !token) return builder.buildResponse(400, {
    verified: false,
    message: “Missing required fields”
    });

    // verify the validation of the current token
    const verificationResponse = auth.verifyToken(username, token);
    if(!verificationResponse.verified) return builder.buildResponse(400, verificationResponse);

    const foundUser = await getUser(username);
    if(!foundUser || !foundUser.username || !foundUser.tokens) return builder.buildResponse(400, {verified: false, message: “Missing Fields detected”});
    if(!foundUser.tokens.includes(token)) return builder.buildResponse(400, {verified: false, message: “User is not authenticated”});

    return builder.buildResponse(200, {
    verified: true,
    message: “success”,
    username,
    token
    });
    }

    // retrieve user data via username from DynamoDB
    const getUser = async (username)=>{
    const params = {
    Key: {
    username: username
    },
    TableName: “login-database”
    }

    return await dynamoDB.get(params).promise().then((response)=>{
    return response.Item;
    }).catch((err)=>{
    return err;
    })
    }

    module.exports.verify = verify;

    Node

    // /services/logout.js file

    const builder = require(“../utils/builder”);
    const bcrypt = require(“bcryptjs”);
    const auth = require(“../utils/auth”);
    const AWS = require(“aws-sdk”);
    const dynamoDB = new AWS.DynamoDB.DocumentClient({
    region: ‘us-east-1’,
    apiVersion: ‘2012-08-10’,
    });

    async function logout(username, token){
    if(!username || !token){
    return builder.buildResponse(400, {message: “Missing required fields”});
    }

    const foundUser = await getUser(username);
    if(!foundUser || !foundUser.username){ // user doesn’t exist in database
    return builder.buildResponse(400, {message: “User doesn’t exist”});
    }

    // user is already logged out
    if(!foundUser.tokens || !foundUser.tokens.includes(token)){
    return builder.buildResponse(400, {message: “User is not authenticated”});
    }
    // remove the current token from the database
    const tokenArray = foundUser.tokens.filter(currToken => currToken !== token);

    const params = {
    Key: {
    username: username
    },
    UpdateExpression: `set tokens = :value`,
    ExpressionAttributeValues: {
    “:value”: tokenArray
    },
    TableName: “login-database”,
    ReturnValues: “UPDATED_NEW”
    };

    return await dynamoDB.update(params).promise().then(()=>{
    return builder.buildResponse(200, {message: “Logged out successfully”});
    }).catch((err)=>{
    return builder.buildResponse(400, {message: err});
    })
    }

    // retrieve user data via username from DynamoDB
    const getUser = async (username)=>{
    const params = {
    Key: {
    username: username
    },
    TableName: “login-database”
    }

    return await dynamoDB.get(params).promise().then((response)=>{
    return response.Item;
    }).catch((err)=>{
    return err;
    })
    }

    module.exports.logout = logout;

    Node

    // /utils/auth.js file

    const jwt = require(“jsonwebtoken”);

    // generate a token encapsulating the username (expires after 1 hour)
    const generateToken = (username)=>{
    if(!username) return null;

    return jwt.sign({username}, process.env.JWT_SECRET, {
    expiresIn: ‘1h’
    })
    }

    // verify the validation of the current token
    const verifyToken = (username, token)=>{
    return jwt.verify(token, process.env.JWT_SECRET, (error, response)=>{
    if(error) return {verified: false, message: “Invalid Token”};
    if(response.username !== username) return {verified: false, message: “Invalid User”};
    return {verified: true, message: “User is verified”};
    })
    }

    module.exports.generateToken = generateToken;
    module.exports.verifyToken = verifyToken;

    Node

    // /utils/builder.js file

    // generate response
    const buildResponse = (statusCode, data)=>{
    return {
    statusCode: statusCode,
    headers: { “Content-Type”: “application/json” },
    body: data,
    };
    }

    module.exports.buildResponse = buildResponse;

    • Now, open any terminal (where we can write npm commands) and write there “npm install” to install necessary dependencies for the project.
    • Now create a zip file selecting the root files and folders and upload the zip file in lambda function. Now, your Lambda function is ready to handle the API requests and send necessary responses.

    AWS Lambda and Amazon DynamoDB for Serverless Authentication

    Serverless Authentication is a method of authenticating users in a cloud-based application without the need for traditional server management. It leverages services like AWS Lambda and Amazon DynamoDB to handle user authentication processes in a cost-effective and efficient manner. By using these serverless components, developers can create secure and responsive authentication systems that scale with the application’s demands. AWS Lambda is a powerful cloud computing service provided by AWS that allows you to run code without provisioning or managing servers. It operates on an event-driven model, meaning it executes functions in response to specific events or triggers, optimizing resource usage and cost efficiency. Amazon DynamoDB, on the other hand, is a highly scalable NoSQL database service also provided by AWS. It’s designed for applications that require fast and predictable performance at any scale. DynamoDB offers seamless scaling of read and write capacity to handle variable workloads, ensuring rapid and reliable data storage.

    In this article, we will learn how to create a Serverless Authentication using AWS Lambda and DynamoDB.

    Similar Reads

    How Authentication works?

    When a user attempts to login into their account, the system prompts them to provide their credentials, which typically includes the username and password. The backend system then undertakes a critical verification process to ascertain the validity of these inputs. If, the provided credentials do not match any existing records, the system promptly communicates this to the user with a clear and concise message: “Wrong username or password”. If the provided credentials are correct, a string is generated i.e. called token which basically encapsulates the username of the user and gets stored in 2 places – a) in browser’s cookies and b) user’s tokens array in database. At the time of verification, whether the user is authenticated or not, we checks first, whether there is any specific token found in browser’s cookies or not. If found, then our backend service breaks the token and find the username encapsulated inside it. Now, it finds the user with that username in database. If no record found, then the user is not authenticated. Otherwise, it checks whether in the founded user’s tokens array, our token is present or not. If it is there, then the user is authenticated, otherwise not....

    Create a DynamoDB Table

    Login to your AWS account and go to DynamoDB service. Click upon the “Create Table” button to create a table “login-database”. For partition key, enter “username”. DynamoDB also offers a new option regarding the provisioning of read & write capacities: On-demand mode. Select the Table Settings as Customised Settings and then in Read Write capacity settings, select the capacity mode “On-demand” This mode is great if you have no idea about how much read and write capacity you will need. And you can of course always switch back to fixed limits, too. If you do know which limits make most sense for you, the traditional “in-advance” provisioning will be cheaper. Click on the “Create Table” button at the end. Now, you can see your created table in the Tables tab....

    Create a Lambda Function

    Now, we will create a lambda function for the backend of our API. The lambda function needs to handle the operations for sign up, sign in, sign out of users....

    Create an API using API Gateway

    Now, we will create our Login-API. We will use the API Gateway service. Let’s understand what we are gonna create, what will happen behind the scenes of the API —...

    Setup Lambda Function

    ...

    Testing our Serverless Authentication API

    Previously, we created the Lambda function, now we have to handle the operations required in serverless authentication. Follow the steps below:...

    FAQs On AWS Lambda and Amazon DynamoDB for Serverless Authentication :

    ...