Harsh Mangalam
Posted on May 14, 2021
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
- create
graphql
dir insrc/
dir - create
util
dir insrc/
dir - create dir
typeDefs
ingraphql
dir - create dir
resolvers
ingraphql
dir - create file
index.js
intypeDefs
dir - create file
index.js
inreslovers
dir - create file
auth.util.js
inutil
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
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!
}
`;
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;
}
},
},
};
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
}
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");
....
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
}
}
}
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"
}
}
}
}
login query
mutation {
login(email:"harshdev@dev.com",password:"123456"){
token
user {
id
name
username
createdAt
role
}
}
}
login query result
{
"data": {
"login": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEwLCJpYXQiOjE2MjA5NTk2MjcsImV4cCI6MTYyMDk5NTYyN30.59OHuy3L5F_0Oes-3kYQwNcsl9vJnTXx-63h0aiVHvc",
"user": {
"id": "10",
"name": "Harsh Mangalam",
"username": "harshdev5301",
"createdAt": "1620959524586",
"role": "USER"
}
}
}
}
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.
Posted on May 14, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.