DEV Community

loading...
Cover image for Secure Password Manager:  MERN stack app- Using Cryptr Encryption Package

Secure Password Manager: MERN stack app- Using Cryptr Encryption Package

Georgey
Looking forward to connect with web devs around the world! Engineer || Content-Writer || Athlete
Originally published at geobrodas.hashnode.dev ・8 min read

Introducing a Secure Password Manager🔐

home.JPG
A secure Password Manager which stores passwords in the encrypted form inside the database to avoid the leak of sensitive information.

Disclaimer: Please don't enter in your original passwords yet and also its an open showcase app, so whatever you enter can be seen to other people with the same link. Use it to experience the different functionalities and dynamic features of the app.

Live Link: https://main.d3qwkjcxzk7m67.amplifyapp.com/

Source Code: https://github.com/GeoBrodas/aws-password-manager/tree/main

Features:👀

  • Dynamic entry of data
  • Fluid animations
  • Fast loading
  • Cross-platform support and responsiveness.
  • Password encryption using aes-256-gcm algorithm.

Tech Stack and Resources⚛️

  • React Library
  • MongoDB Atlas
  • NodeJs
  • Express
  • Cryptr NPM module for NodeJs.
  • Axios for Api requests.
  • Material UI
  • Heroku for Back-end And Front-end on AWS-Amplify.

Inspiration💡

I usually store my passwords in the browser itself, but one fine day it turned out that my google browser just popped out a warning saying there was a data breach in their database and that there was an urgent need to change certain passwords because they were exposed!

databreach.JPG

And then I just got tired of changing all the passwords and wondered....what if could make my own password manager which will be encrypted in the database with fewer chances of being exposed and of course, only I will be having access to the app, and that's when I started building a secure Password manager using React and Cryptr for encryption on the server-side. So without further ado let's get started with the development journey!

Stages of the App development Journey🚶🏽‍♂️

So the first thing I divided my build procedure into 5 stages so that I could focus on each stage every day and in 5 days I would be ready with the app:-

Colour Inspiration🎨

This was really cool. I took the color palette used in the Rescue Armour from the "Iron Man: Armoured Adventures" animated series.

#bloggg-3.png

1. Front-End side using react for dynamic rendering. 👁️

So for the first day, I focussed on the front-end side, basically, I wanted to create cards, and delete them on button click all to be rendered dynamically. For this, we need firstly a form with two inputs that could take in the account name and password entered by the user. And then the card component which would display the entered credentials of the user. And subsequently, each card should contain a delete button. To add the card as usual I gave onChange attributes to both the inputs and used the useState Hook to store the credentials entered. To submit the information, I then created a prop that would take in one parameter and that is the credit object declared for the useState.

const [credit, setCredit] = useState({
    accName: "",
    pass: "",
  });

function handleChange(event) {
    const { name, value } = event.target;

    setCredit((prevNote) => {
      return {
        ...prevNote,
        [name]: value,
      };
    });
  }

  function submitCred(event) {
    props.onAdd(credit);
    setCredit({
      accName: "",
      pass: "",
    });
    event.preventDefault();
  }
Enter fullscreen mode Exit fullscreen mode

I then passed these props to my main App.jsx file.

const [allCreds, setCred] = useState([]);

function addCred(newCred) {
    setCred((prevCreds) => {
      return [...prevCreds, newCred];
    });
  }

 <InputTextArea onAdd={addCred} />
Enter fullscreen mode Exit fullscreen mode

This would store the data in the allCreds array as objects which then can be used to render all the information into card components using the Map function.

<div className="flexbox">
        {allCreds.map((cred, index) => {
          return (
            <Card
              key={index}
              id={index}
              name={cred.accName}
              pass={cred.pass}
              onDelete={deleteCred}
            />
          );
        })}
      </div>
Enter fullscreen mode Exit fullscreen mode

This Card component would take in another prop called onDelete which will return the index of the card which was returned when the onClick event was triggered.
id is set by the index parameter via the Map function.

function removeCard() {
    props.onDelete(props.id);
  }

Enter fullscreen mode Exit fullscreen mode

In the main App.jsx file the delete function contains a filter function that will return all the card components excluding the card component whose index was passed to the delete function.

function deleteCred(mid, id) {
setCred((prevCreds) => {
      return prevCreds.filter((cred, index) => {
        return index !== id;
      });
    });
 }
Enter fullscreen mode Exit fullscreen mode

With this, we achieve all the full front-end objectives in our React-application!

2. Setting up MongoDB database and Read, Create, and delete them from the Front-End side. 🗂️

Firstly you need to have a MongoDB Atlas for hosting your database on the cloud. MongoDB has a free tier plan of 512 MB, which can be used to test early-stage apps. Then I connected my application with the MongoDB database. First thing I installed express, cors, and mongoose on the back-end, and on the front-end Axios to make API requests to the back-end. Cors will help to make a connection between our back-end and front-end.

The schema model for every request we make to the MongoDb database will be as follows:-

const CredSchema = new mongoose.Schema({
  accName: {
    type: String,
    required: true,
  },
  pass: {
    type: String,
    required: true,
  },
});

Enter fullscreen mode Exit fullscreen mode

So after all the setup let's head towards the first task: - To submit our credentials from the front-end to the back-end. We can do this by making an Axios Post request when the user clicks the submit button.

Axios.post("https://localhost:3001/insert", {
      accName: newCred.accName,
      pass: newCred.pass,
    });
Enter fullscreen mode Exit fullscreen mode

On the server-side, we have to make a post route to receive the API request from Axios and then use Mongoose to create the entry into the database.

app.post("/insert", async (req, res) => {
  const name = req.body.accName;
  const password = req.body.pass;
  const newCred = new CredModel({
    accName: name,
    pass: password,
  });

  try {
    newCred.save();
    res.send("Inserted Data");
  } catch (err) {
    console.log(err);
  }
});
Enter fullscreen mode Exit fullscreen mode

One job is done, 2 to go! Now we have to render all the information from the database to the front-end went the page loads. For this, we can use the useEffect hook to make an Axios Get request when the page first loads. The response that the request returns can be used to then set the state of allCreds state.

useEffect(() => {
    Axios.get("https://localhost:3001/read").then(
      (response) => {
        setCred(response.data);
      }
    );
  }, []);
Enter fullscreen mode Exit fullscreen mode

And finally, the tricky part to delete the card when the user clicks the delete button.
Now when the Get requests return all the data from the database, it returns a unique ID with the property name _id. Let's name the mid as in for MongoDB id. We can get hold of this mid from the map function we created to render all the credentials from the database.

Note: I don't want all the code to clutter and hence I'm removing some props to make you'll understand😊

{allCreds.map((cred, index) => {
          return (
            <Card
              key={index}
              mid={cred._id}
            />
          );
        })}
Enter fullscreen mode Exit fullscreen mode

This prop can be passed to the delete function in our card component as a second parameter.

function removeCard() {
    props.onDelete(props.mid, props.id);
  }

Enter fullscreen mode Exit fullscreen mode

In our App.jsx file we pass this mid prop.
In our Axios delete request, the URL here is enclosed within back-ticks instead of "". This is a really useful feature of Javascript. Note how we are passing the mid prop to the back-end by enclosing it within a ${mid}.

function deleteCred(mid, id) {
    setCred((prevCreds) => {
      return prevCreds.filter((cred, index) => {
        return index !== id;
      });
    });

    Axios.delete(`https://localhost:3001/delete/${mid}`); 
//use back-tickssss--importantttt!!!!
  }

Enter fullscreen mode Exit fullscreen mode

On our server-side, we will then make a delete route and use the mongoose findByIdAndRemove method to look through the database for the entry matching with the mid and remove it instantly.

app.delete("/delete/:id", async (req, res) => {
  const id = req.params.id;
  await CredModel.findByIdAndRemove(id).exec();
  res.send("deleted item: " + id);
});
Enter fullscreen mode Exit fullscreen mode

3. Encryption at server-side and decryption to show password.🔐

cryptr.JPG

For encryption to store our passwords in encrypted form, we can use a simple npm package called cryptr. Now we can set this up by making a secret key, after which we can encrypt and decrypt strings by simply calling the encrypt and decrypt function provided by cryptr.

const Cryptr = require("cryptr");
const cryptr = new Cryptr("yoursecretkey");
Enter fullscreen mode Exit fullscreen mode

We want the passwords to be encrypted as soon as we receive the post request from Axios on the client-side.

const name = req.body.accName;
const password = cryptr.encrypt(req.body.pass);
  const newCred = new CredModel({
    accName: name,
    pass: password,
  });
Enter fullscreen mode Exit fullscreen mode

The passwords will be now encrypted in our database using the aes-256-gcm algorithm.

dbdata.JPG

Now trust me, this was the toughest part in the development, that is to display the original password to the user when the user clicks the 👁 button.
Now what I did, is to make the user trigger the onClick event and pass it a function that takes two parameters and then passes that to the main App.jsx file.

function showP() {
    props.seePassword(props.pass, props.id);
  }
Enter fullscreen mode Exit fullscreen mode

In my main App.jsx file, I passed this prop as a function to the card element which is in the Map function.

{allCreds.map((cred, index) => {
          return (
            <Card
              key={index}
              id={index}
              seePassword={getPassword}
              pass={cred.pass}
             />
          );
        })}
Enter fullscreen mode Exit fullscreen mode

In our getPassword function, we are passing the encrypted password which can be tapped with the Map function and using Axios make a post request to the server-side to send all the decrypted passwords back to the front-end side.

//App.jsx file
function getPassword(password, id) {
    Axios.post("https://localhost:3001/showpassword", {
      password: password,
    }).then((response) => {
      setCred(
        allCreds.map((cred, index) => {
          return index === id
            ? {
                accName: response.data,
                pass: cred.pass,
              }
            : cred;
        })
      );
    });
  }

//index.js file at server-side
app.post("/showpassword", (req, res) => {
  res.send(cryptr.decrypt(req.body.password));
});
Enter fullscreen mode Exit fullscreen mode

The response which we get from the server-side containing all the passwords can be run through a map function. The map function only returns the decrypted password back which gets matched with the id of the card component which the user clicked on. Using the ternary operator, we can use the setCred function from the useState to set the state of allCreds array by making the name of the credential equal to the response.

4. Making our code more leak-safe use environment variables.🛡️

This is best done using an npm package called dotenv.

dotenv.JPG

Remember the secret we stored our key. Well if you're committing your code to GitHub frequently this key will easily get exposed, if anyone refers to your code changes by referring to the commits. So make sure you store your environment variables first, add to the .gitignore file, and then commit to your remote repository.

//index.js file ---Server side
//require the dotenv module at the earliest in your file.
require("dotenv").config();

const cryptr = new Cryptr(process.env.SECRET);

//.env file ----Server side
SECRET=yoursecretkey
Enter fullscreen mode Exit fullscreen mode

Make sure to follow the format dotenv module specifies, that is, the constant has to be entirely capitalized with no quotes surrounding the key.

5. Deploying 🚀

I deployed my server file to Heroku. It had been a while since I had used Heroku, came across a lot of errors, but somehow managed to deploy it by seeing some videos and referring to the documentation.

I know I had to deploy the production build of React and not the development build. I had never deployed a React project ever but I directly went to the AWS Amplify console and it auto-generated the build settings for me, which was surprising for me because hosting platforms like Netlify don't and the developer has to mention the build settings. The process hardly took me 4 mins, and the app was up and running!🚀

deployed on aws.JPG

Thank you for reading till here!

Hope you liked this blog, if yes hit a ❤

Do let me know if you want the source code for the back-end part. I didn't share it for security reasons, as there are environment variables in it and the password to the MongoDB cluster.

Hit me up or retweet this blog if you liked it.

For me building this app was a big leap because I didn't have the confidence to build a full-stack app like this.

Discussion (0)