Journey to the real world by cloning DEV.to backend server(part 5)

harshmangalam

Harsh Mangalam

Posted on May 14, 2021

Journey to the real world by cloning DEV.to backend server(part 5)

In the last post we have created our first model user model and started our development server.
In this post we will work on jwt authentication using jsonwebtoken package.

Authentication workflow in our platform

when users will visit our platform first time they do not have any token so they will be unauthenticated and unauthorized.

In Simple term UnAuthenticated user means they have not loggedin in our platform and UnAuthorized means they may or may not be login but definitely not allowed for specific pieces of actions.
For example: In our Platform any one can read posts no required for login but to create post they must have to login. If someone is moderator then it have some specific role for that. Normal reader cannot make an action which are specially designed for moderators.

When user will login then from backed they will get a jwt token. Then from next request they will provide that jwt token in Authentication header and backend will decode that and recognise who is the user in this token.

We knew that HTTP is stateless they have no idea how to manage user session because our backend is on localhost:4000 nodejs server and frontend may be on localhost:3000 React client it will not possible for http to remember user.
Hence we have a way to handle this kind of problems

We will generate a jwt token for every loggedin user and will send to him they will put this token in localstorage and for further request they will send the token inside http headers.

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

You can found more about jwt here.

Setup schema and resolvers for authentication

create some directory to chunk our logic in different file and folder

  1. create graphql dir in src/ dir
  2. create util dir in src/ dir
  3. create dir typeDefs in graphql dir
  4. create dir resolvers in graphql dir
  5. create file index.js in typeDefs dir
  6. create file index.js in reslovers dir
  7. create file auth.util.js in util dir

when we will proceed in this series then we will see how we will break schema and resolvers in multiple file with ease

devblog_server/src/typeDefs/index.js

This file will contain all the query, mutation and subscription .

For authentication we will use jsonwebtoken and for password hashing we will use bcrypt library which is freely available in npm

pnpm add jsonwebtoken bcrypt

Enter fullscreen mode Exit fullscreen mode

Lets create authentication query and mutation

src/graphql/typeDefs/index.js

const { gql } = require("apollo-server-express");

module.exports = gql`
  type AuthResponse {
    token: String!
    user: User!
  }

   type Mutation {
    login(email: String!, password: String): AuthResponse!
    register(name: String!, email: String!, password: String!): AuthResponse!
  }
`;



Enter fullscreen mode Exit fullscreen mode

And now create add resolvers for above query

src/graphql/resolvers/index.js

const { UserInputError, AuthenticationError } = require("apollo-server-errors");
const {
  generateHash,
  generateUsername,
  matchPassword,
  generateJwtToken,
} = require("../../utils/auth.util");

module.exports = {

  Mutation: {
    // login user
    async login(_, { email, password }, { prisma }) {
      try {
        const user = await prisma.user.findUnique({
          where: {
            email,
          },
        });

        if (!user) {
          throw new UserInputError("USER_NOT_FOUND", {
            message: "Account with this email does not found create new one",
          });
        }

        const matchPass = await matchPassword(password, user.password);

        if (!matchPass) {
          throw new UserInputError("INCORRECT_PASSWORD", {
            message: "Password is incorrect",
          });
        }

        const token = generateJwtToken(user.id);

        return {
          user,
          token,
        };
      } catch (error) {
        return error;
      }
    },

    // create new account
    async register(_, { name, email, password }, { prisma }) {
      try {
        const checkEmail = await prisma.user.findUnique({
          where: {
            email,
          },
        });

        if (checkEmail) {
          throw new UserInputError("EMAIL_ALREADY_EXISTS", {
            message: "Account with this email is already exists ",
          });
        }

        username = generateUsername(email);
        password = await generateHash(password);

        const newUser = await prisma.user.create({
          data: {
            name,
            email,
            password,
            username,
          },
        });

        const token = generateJwtToken(newUser.id);

        return {
          token,
          user: newUser,
        };
      } catch (error) {
        return error;
      }
    },
  },
};

Enter fullscreen mode Exit fullscreen mode

above we have implemented registration and login for users when we will proceed in series i will show you how easily we will create admin user from environment variable.

src/util/auth.util.js

const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
exports.generateUsername = (email) => {
  const max = 9990;
  const min = 1000;

  return (
    email.split("@")[0] + Math.floor(Math.random() * (max - min))
  );
};

exports.generateHash = async (password) => {
  const hash = await bcrypt.hash(password, 10);
  return hash;
};

exports.matchPassword = async (password, hashPassword) => {
  const hasMatch = await bcrypt.compare(password, hashPassword);
  return hasMatch;
};

exports.generateJwtToken = (userId) => {
  return jwt.sign({ userId }, process.env.JWT_SECRET, { expiresIn: "10h" });
};


exports.decodeJwtToken = (token) => {
  const {userId}  = jwt.verify(token,process.env.JWT_SECRET)
  return userId
}
Enter fullscreen mode Exit fullscreen mode

This is utility file which contain utility function related to authentication.

Last thing to do is update src/server.js by typeDefs and resolvers.

server.js

....
const typeDefs = require("./graphql/typeDefs");
const resolvers = require("./graphql/resolvers");

....


Enter fullscreen mode Exit fullscreen mode

Save all the stuff and go to graphql playground at localhost:4000/graphql and start hacking around authentication

register query

mutation {
  register(name:"Harsh Mangalam",email:"harshdev@dev.com",password:"123456"){
    token
   user {
    id
    name
    username
    createdAt
    role

  } 
  }
}

Enter fullscreen mode Exit fullscreen mode

This will give you result like this

{
  "data": {
    "register": {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEwLCJpYXQiOjE2MjA5NTk1MjQsImV4cCI6MTYyMDk5NTUyNH0.xmdJYVpZUxcUhr5CBQwR1C7yLjKSEvAmjt7gr2sjsNw",
      "user": {
        "id": "10",
        "name": "Harsh Mangalam",
        "username": "harshdev5301",
        "createdAt": "1620959524586",
        "role": "USER"
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

login query

mutation {
  login(email:"harshdev@dev.com",password:"123456"){
    token
   user {
    id
    name
    username
    createdAt
    role

  } 
  }
}
Enter fullscreen mode Exit fullscreen mode

login query result

{
  "data": {
    "login": {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEwLCJpYXQiOjE2MjA5NTk2MjcsImV4cCI6MTYyMDk5NTYyN30.59OHuy3L5F_0Oes-3kYQwNcsl9vJnTXx-63h0aiVHvc",
      "user": {
        "id": "10",
        "name": "Harsh Mangalam",
        "username": "harshdev5301",
        "createdAt": "1620959524586",
        "role": "USER"
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

In our next post we will break our graphql schema and resolvers into chunk so that development will easier and we will work more on user and profile sections.

💖 💪 🙅 🚩
harshmangalam
Harsh Mangalam

Posted on May 14, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related