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.
- Create a package.json file in the root directory with the following content.
Node
{
“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
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
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
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
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
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
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
// 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.