Unlocking Secure User Authentication with JSON Web Tokens and Bcrypt

Setting Up the Foundation

To get started, we’ll create a new Apollo Server project using Prisma as the ORM. First, let’s set up the project directory with a package.json file.

{
  "name": "apollo-server-prisma",
  "version": "1.0.0",
  "scripts": {
    "start": "node index.js"
  }
}

Next, we’ll create an index.js file to bootstrap the application.

const { ApolloServer } = require('apollo-server');
const { PrismaClient } = require('@prisma/prisma-client');

const prisma = new PrismaClient();

const server = new ApolloServer({
  //...
});

server.listen().then(({ url }) => {
  console.log(`Server ready at ${url}`);
});

Configuring Prisma and PostgreSQL

To use Prisma as the ORM, we’ll need to have Docker installed. We’ll configure PostgreSQL as the database of choice on the Docker host. After running the necessary commands, we’ll have the required files generated from the datamodel.prisma file.

docker run -d --name prisma-postgres -e POSTGRES_PASSWORD=mysecretpassword -p 5432:5432 postgres

Restructuring the Project

Now that we have Prisma set up, let’s restructure our project by creating schema.js and resolvers.js files in the project root. We’ll also update our index.js file to import the Prisma instance.

// schema.js
const { gql } = require('apollo-server');

const typeDefs = gql`
  type Query {
    me: User
  }

  type Mutation {
    signUp(name: String!, email: String!, password: String!): AuthPayload!
    login(email: String!, password: String!): AuthPayload!
  }

  type AuthPayload {
    token: String!
    user: User!
  }

  type User {
    id: ID!
    name: String!
    email: String!
  }
`;

module.exports = typeDefs;

Installing Required Libraries

To proceed, we’ll need to install a few libraries, including bcrypt and jsonwebtoken. We’ll also add a script to our package.json file to enable us to start our server with ease.

npm install bcrypt jsonwebtoken

Updating the Datamodel and Schema

Next, we’ll update our datamodel.prisma file to include user authentication fields.

model User {
  id       String   @id @default(cuid())
  name     String
  email    String   @unique
  password String
}

We’ll then update our schema.js file to include mutations for signing up and logging in users.

// resolvers.js
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');

const resolvers = {
  Mutation: {
    async signUp(parent, { name, email, password }) {
      const hashedPassword = await bcrypt.hash(password, 10);
      const user = await prisma.createUser({ name, email, password: hashedPassword });
      const token = jwt.sign({ userId: user.id }, 'ysecretkey', { expiresIn: '1h' });
      return { token, user };
    },
    async login(parent, { email, password }) {
      const user = await prisma.getUser({ email });
      if (!user) {
        throw new Error('Invalid email or password');
      }
      const isValid = await bcrypt.compare(password, user.password);
      if (!isValid) {
        throw new Error('Invalid email or password');
      }
      const token = jwt.sign({ userId: user.id }, 'ysecretkey', { expiresIn: '1h' });
      return { token, user };
    },
  },
};

module.exports = resolvers;

Validating User Identity

To validate the user’s identity, we’ll modify the context function to pass the token from the client to the server. We’ll create an authenticate.js file to handle user authentication and update our resolvers to reflect these changes.

// authenticate.js
const jwt = require('jsonwebtoken');

const authenticate = async (context) => {
  const token = context.request.headers.authorization;
  if (!token) {
    throw new Error('Not authorized');
  }
  const { userId } = jwt.verify(token, 'ysecretkey');
  return { userId };
};

module.exports = authenticate;

Decoding Tokens and Authorization

To make our decoded token more versatile, we’ll update it to handle authorization. We’ll also supply our login token via the HTTP HEADERS section in the GraphQL playground.

mutation {
  login(email: "[email protected]", password: "mypassword") {
    token
  }
}

In the HTTP HEADERS section:

Authorization: Bearer YOUR_TOKEN_HERE

Leave a Reply