38. Generating a JWT

When users log in to PrintBay, we're going to generate a JSON Web Token and send it back to them.

This token will also be stored on the user model so that when the token is later used to authenticate a call, we can match the token to the user.

This means we need to enhance our User schema to store the token by adding a new property token and making it's type a String.

server/models/user.js

const UserSchema = mongoose.Schema({
  name: { ... },
  email: { ... },
  password: { ... },
  token: {
    type: String
  }
});

Next, we'll write a method on the model that will generate the token.

Mongoose methods

Before we do that, we need to be aware of the types of methods that are available for Mongoose.

There are two types: statics methods and instance methods.

Static methods are called on a model. For example, the findById method.

const user = User.findById(id);

Static methods are generally used to query or update one or more documents.

Instance methods, on the other hand, are applied to an individual model that has been returned from the database, or perhaps newly created. For example, the save instance method.

const user = new User();
user.save();

Finally, it's important to realize that we can add custom methods to our Mongoose schema, allowing us to extend the features of Mongoose as we'll be doing in this video.

Generating auth token

We're now going to generate our JWT. Let's install the library jsonwebtoken.

$ npm i -S jsonwebtoken

This library will abstract most of the logic about generating JWTs, making things much simpler for us.

We'll now require this package at the top of our server/models/user.js file. We'll declare a variable jwt and require the "jsonwebtoken" package.

Custom instance method

Let's then add a custom instance method to our User schema. Instance methods are added to the methods object on the schema. We'll call this method generateAuthToken.

The idea of this method is that it can be called from any Express endpoint where we load the user. It's going to do two things - firstly, it will generate a token and save it to the user model. Next, it will return the token.

We'll assign to this property a regular async function. Note that we don't use an arrow function here, as we want to use the this keyword which will refer to the specific document that we're running this method on.

server/models/user.js

const jwt = require("jsonwebtoken");

...

UserSchema.methods.generateAuthToken = async function () {

};

Generate token

Now we can generate our JWT. Let's create a constant token. Then we'll use the jsonwebtoken package and use the sign method to generate the complete token.

The sign method has two arguments. The first is the payload for your JWT. Remember that this need to include an ID for the user that the server can use to identify the user.

So let's use the Mongo document ID of that user. We can do so by creating a _id property in the payload. We'll assign the user ID which is a property of the context obect. We'll use the toHexString method here as well.

The second argument to sign is the secret value that we use to salt our hash. You can make this any string value you like.

Finally, we'll call the toString method at the end of sign so that we can now use this token as a string.

server/models/user.js

UserSchema.methods.generateAuthToken = async function () {
  const token = jwt.sign(
    { _id: this._id.toHexString() },
    "secret string"
  ).toString();
};

Save token

Now that we have the token, we'll save it to the model by going this.token = token and we'll then await the saving of the model.

When the save process completes, we return the token to the method that calls it.

server/models/user.js

UserSchema.methods.generateAuthToken = async function () {
  ...
  this.token = token;
  await this.save();
  return token;
};

Putting secret into .env file

Before we move on, we should move our secret string into the environment file as we don't want this hardcoded.

Let's create a new variable in the .env.server file called JWT_SECRET. You can give this whatever value you want. I'm just going to mash the keyboard and see what comes out.

PORT=8070
JWT_SECRET=9328729ef7ef9wjdfjs9

Let's now go back to our model and replace the hardcoded string with the environment variable by going process.env.JWT_SECRET.

server/models/user.js

const token = jwt.sign(
  { _id: this._id.toHexString() },
  process.env.JWT_SECRET
).toString();
Discussion

0 comments