Translation | "`JavaScript` `Everywhere` Chapter `7` User Accounts and Authentication (^`_`^)

Translation | " JavaScript Everywhere" 7Chapter User Accounts and Authentication ( `_` )

Write at the top

Hello everyone, I am Mao Xiaoyou, a front-end development engineer. An English technical book is being translated.

In order to improve everyone's reading experience, the structure and content of sentences are slightly adjusted. If you find any flaws in this article, or if you have any comments or suggestions, you can leave a message in the comment area, or add my WeChat:, code\_maomaowelcome to communicate and learn.

(σ゚∀゚)σ ..:*☆Oh, good

Chapter 7 User Accounts and Authentication

Imagine walking in a dark alley, and you are about to join a "cool secret club" (if you are reading this book, you are a well-deserved member). When you enter the secret door of the club, the receptionist will greet you and hand you a form. On the form, you must fill in your name and password, only you and the receptionist can know. After filling out the form, hand it to the receptionist, who will enter the back lobby of the club. In the background, the receptionist uses the key to encrypt your password, and then stores the encrypted password in the locked file vault. Then, the receptionist will stamp the token, and then stamp your only member ID. Back in the lobby, the receptionist handed you the pass, and you stuffed the pass into your pocket. Now, every time you return to the club, you only need to show your pass to enter. This interaction sounds like something from a low-cost spy movie, but it is almost the Websame process that we follow every time we sign up for the app.

In this chapter, we will learn how to build a GraphQLmodification that will allow users to create an account and log in to our application. We will also learn how to encrypt a user's password and return a token to the user, which they can use to verify their identity when they interact with our application.

Application certification process

Before we begin, let's take a step back and determine the process that a user will follow when registering an account and logging into an existing account. If you don’t understand all the concepts presented here, don’t worry: we will solve them step by step.

First, let’s review the account creation process:

  1. The user enters the email address, user name, and password into the user interface ( UI), for example GraphQL Playground, Weban app or mobile app.

  2. The user interface uses the user information to send the GraphQLrequest to our server.

  3. The server encrypts the password and stores the user information in the database.

  4. The server returns a token to the user interface, which contains the user's ID.

  5. UIStore this token for a specified period of time and send it to the server with each request to authenticate the user.

Now let us look at the user login process:

  1. Users UIenter their email or username and password in the fields.

  2. UIThis information will be used GraphQLto send requests to our servers.

  3. The server decrypts the password stored in the database and compares it with the password entered by the user.

  4. If the passwords match, the server will return a token to the user interface, which contains the user's ID.

  5. UIStore this token for a specified period of time and send it to the server with each request.

As you can see, these processes are very similar to our "Secret Club" process. In this chapter, we will focus on the APIparts that implement these interactions .

Password reset process

You will notice that our app does not allow users to change their passwords. We can allow users to change the password, but it is safer to verify the reset request via email first. For brevity, we will not implement the password reset feature in this book, but if you are interested in examples and resources for creating a password reset process, please visit the JavaScript Everywhere Spectrumcommunity.

Encryption and token

When exploring the user authentication process, I mentioned encryption and tokens. These sounds like dark art in mythology, so let's take a moment to study each one carefully.

Encryption password

In order to effectively encrypt user passwords, we should combine hashing and salting.

Hash

It is the act of confusing it by converting a text string into a seemingly random string. The hash function is "a way", which means that once the text is hashed, it cannot be restored to the original string. After the password is hashed, the plain text of the password will never be stored in our database.

Salting out

It is the act of generating a random data string, which will be used with the hash password. This ensures that even if two users have the same password, the hashed and salted version will be unique.

bcryptBased on the blowfish password, it is usually used in a series of network frameworks. In Node.jsdevelopment, we can use the bcryptmodule for password hashing and salting.

In our application code, we will need to use bcryptthe module and a function to process write salting and hashing.

Salting out and hashing example

The following example is for illustration purposes only. Later in this chapter, we will salt out, hash and bcryptintegrate passwords together.

// require the module
const bcrypt = require('bcrypt');

// the cost of processing the salting data, 10 is the default
const saltRounds = 10;

// function for hashing and salting
const passwordEncrypt = async password => {
  return await bcrypt.hash(password, saltRounds)
};

In this example, I can pass the password PizzaP@ rty99and the resulting salting out is '2 a' `2a`' 2 a '10 'HF 2 rs. I YS v X 1 l 5 FP r X 697 O' and `HF2rs.iYSvX1l5FPrX697O` and' H F 2 r s . I Y S v X 1 l 5 F P r X 6 9 7 O ' and2a '10' `10`HF2rs.iYSvX1l5FPrX697O9dYF/O2kwHuKdQTdy.7oaMwVga54bWGThe hashed password of ' 1 0 ' . (Salt out plus encrypted password string).

Now, when the password checking cryptographic hashing and salting the user, we will use bcryptthe comparemethod:

// password is a value provided by the user
// hash is retrieved from our DB
const checkPassword = async (plainTextPassword, hashedPassword) => {
    
    
  // res is either true or false
  return await bcrypt.compare(hashedPassword, plainTextPassword)
};

By encrypting the user password, we can store it securely in the database.

JSON WebToken

As a user, it would be very annoying if we need to enter a username and password every time we want to visit a protected page of a site or application. Instead, we can store the user with a JSON Webtoken IDsecurely in the user's device.

For every request the user makes from the client, they can send the token, and the server will use the token to identify the user.

JSON WebThe token ( JWT) contains three parts:

  • Header Header

    General information about the token and the type of signature algorithm being used

  • Payload Payload

    Information we intentionally store in the token (e.g. username or ID)

  • signature Signature

    AmeansVerification token

Let's take a look at the token, it seems to be composed of random characters, separated by periods:

xx-header-xx.yy-payload-yy.zz-signature-zz

In our application code, we can use jsonwebtokenmodules to generate and verify our tokens. To this end, we pass on the information we wish to store and .envthe secret password that is usually stored in our files.

const jwt = require('jsonwebtoken');

// generate a JWT that stores a user id
const generateJWT = await user => {
  return await jwt.sign({ id: user._id }, process.env.JWT_SECRET);
}

// validate the JWT
const validateJWT = await token => {
  return await jwt.verify(token, process.env.JWT_SECRET);
} 

JWT and meetings

If you have Webused user authentication in your application before , you are likely to be exposed to user sessions.

Session information is usually stored locally cookie, and based on the data storage area in the memory (for example

Redis, Although traditional databases can also be used. Regarding JWTor conversation, which one has a better argument? But I find that it JWTprovides the most flexibility, especially when integrating with non- Webenvironments (such as native mobile applications). Although the session can GraphQLuse a good match, but it JWTstill is GraphQL Foundationand Apollo Serverrecommended method document.

Through use JWT, we can safely return the user IDand store it in the client application.

Integrate authentication into our API

Now that you have a deep understanding of the components of user authentication, we will implement the ability for users to register and log in to our application. To this end, we will update GraphQLand Mongoosemode, write signUpand signInmodify the parser to generate a user token, and token verification request to each server.

User structure

First, we will add the Usertype and update the Notetype of authorfield, a reference Userto the update GraphQLmode. To do this, the update src/schema.jsfile:

 type Note {
    
    
 id: ID!
 content: String!
 author: User!
 createdAt: DateTime!
 updatedAt: DateTime!
}

type User {
    
    
 id: ID!
 username: String!
 email: String!
 avatar: String
 notes: [Note!]!
} 

When users register for our app, they will submit their username, email address and password. When users log in to our app, they will send a modification containing their username or email address and password. If you register or log in successfully modified, it APIwill be returned as a string token. In order to accomplish this in our mode, we will need src/schema.jsto add two new variables to the file, and each variable will return String, which will be ours JWT:

type Mutation {
  ...
  signUp(username: String!, email: String!, password: String!): String!
  signIn(username: String, email: String, password: String!): String!
} 

Now that our GraphQLmodel has been updated, we also need to update the database module. For this, we will src/models/user.jscreate a Mongoosepattern file in. The settings of this file will be similar to our note module file, which contains fields for username, email, password and avatar. By setting index: { : }, we will also require the username and email fields to be unique in our database. uniquetrue

To create a user database module, src/models/user.jsenter the following in the file:

const mongoose = require('mongoose');

const UserSchema = new mongoose.Schema(
  {
    username: {
      type: String,
      required: true,
      index: { unique: true }
    },
    email: {
      type: String,
      required: true,
      index: { unique: true }
    },
    password: {
      type: String,
      required: true
    },
    avatar: {
      type: String
    }
  },
  {
    // Assigns createdAt and updatedAt fields with a Date type
    timestamps: true
  }
);

const User = mongoose.model('User', UserSchema);
module.exports = User;

After placing the good of our user module file, we must now update src/models/index.jsto export module:

const Note = require('./note');
const User = require('./user');

const models = {
  Note,
  User
};

module.exports = models; 

Authentication parser

After writing the GraphQLsum Mongoosepattern, we can implement the parser so that users can register and log in to our application.

First, we need to .envadd a file environment JWT\_SECRETvariable. The value should be a string without spaces. It will be used to JWTsign ours , which allows us to verify them when decoding them.

JWT_SECRET=YourPassphrase

After creating this variable, the required packages can import our mutation.jsfile. We will use a third-party bcrypt, jsonwebtoken, mongooseand dotenvpackage, and import the Apolloserver AuthenticationErrorand ForbiddenErrorutilities.

In addition, we will import the gravatarutility function, which is already included in the project. This will generate a picture image in the user's email address URL.

In src/resolvers/mutation.js, enter the following:

const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const {
  AuthenticationError,
  ForbiddenError
} = require('apollo-server-express');
require('dotenv').config();

const gravatar = require('../util/gravatar'); 

Now we can write our signUprequest. This request will accept username, email address and password as parameters. We will normalize email addresses and usernames by modifying all spaces and converting them to all lowercase letters. Next, we will use the bcryptmodule to encrypt user passwords. We will also helpergenerate Gravatarimages for user avatars by using our library URL. After performing these operations, we will store the user data in the database and return the token to the user.

We can try/catchmake all the settings in the block, and if there are any problems during the registration process, our parser can return vague errors to the client.

To accomplish all of these operations, in the src/resolvers/mutation.jspreparation of the file signUprequested, as follows:

signUp: async (parent, { username, email, password }, { models }) => {
   // normalize email address
   email = email.trim().toLowerCase();
   // hash the password
   const hashed = await bcrypt.hash(password, 10);
   // create the gravatar url
   const avatar = gravatar(email);
   try {
     const user = await models.User.create({
       username,
       email,
       avatar,
       password: hashed
     });

     // create and return the json web token
     return jwt.sign({ id: user._id }, process.env.JWT_SECRET);
   } catch (err) {
     console.log(err);
     // if there's a problem creating the account, throw an error
     throw new Error('Error creating account');
   }
 }, 

Now, if we switch to the browser GraphQL Playground, we can try signUpthe request. For this, we will write a GraphQLmodification using username, email and password values :

mutation {
    
    
  signUp(
    username: "BeeBoop",
    email: "[email protected]",
    password: "NotARobot10010!"
  )
}

When we run the request, the server will return our Such a token (FIG. 7- 1):

"data": {
    
    
   "signUp": "eyJhbGciOiJIUzI1NiIsInR5cCI6..."
 }
} 

[External link image transfer failed. The source site may have an anti-leech link mechanism. It is recommended to save the image and upload it directly (img-HLrver5v-1606266940860)(http://vipkshttp0.wiz.cn/ks/share/resources/c46f74f8-50d4 -4015-8658-189fa6382bb9/871b82ed-ac91-46a1-b94d-9de289ff2ab6/index_files/4784556b-d63a-4693-a975-096dde03f6cd.jpg)]

Figure 7-1 . GraphQL PlaygroundThe signUprequest

The next step will be to write our login request.

This request will accept the user's username, email and password. Then, it will find the user in the database based on the username or email address. After finding the user, it will decrypt the password stored in the database and compare it with the password entered by the user. If the username and password match, our application will return a token to the user. If they do not match, we will throw an error.

In the src/resolvers/mutation.jsfile, write this request as follows:

 signIn: async (parent, { username, email, password }, { models }) => {
   if (email) {
      // normalize email address
      email = email.trim().toLowerCase();
    }

   const user = await models.User.findOne({
     $or: [{ email }, { username }]
   });

   // if no user is found, throw an authentication error
   if (!user) {
     throw new AuthenticationError('Error signing in');
   }

   // if the passwords don't match, throw an authentication error
   const valid = await bcrypt.compare(password, user.password);
   if (!valid) {
     throw new AuthenticationError('Error signing in');
   }

   // create and return the json web token
   return jwt.sign({ id: user._id }, process.env.JWT_SECRET);
 }

Now, we can access in your browser GraphQL Playground, and use by signUpattempting to account request to create signIna request:

mutation {
    
    
  signIn(
    username: "BeeBoop",
    email: "[email protected]",
    password: "NotARobot10010!"
  )
} 

Similarly, if successful, we should adopt changes JWTto resolve (Figure 7- 2):

{
    
    
  "data": {
    
    
    "signIn": "<TOKEN VALUE>"
  }
} 

Figure 7-2 . GraphQL PlaygroundThe signInmodification

With these two resolvers, users will be able to JWTregister and log in to our application. To do this, try to add more accounts, or even deliberately enter incorrect information (such as mismatched passwords) to see what is GraphQL APIreturned.

Add user to parser context

Now that the user can use GraphQLrequests to receive a unique token, we will need to verify that token on each request.

We expect that our client (whether it is web, mobile or desktop) will send an HTTPauthorized token with the header mentioned in the request .

We can then HTTPuse JWT\_SECRETto read the token from the header, decode it using variables, and pass user information and context to each GraphQLparser. By doing this, we can determine whether the logged-in user is making a request, and if he is making a request, then it is that user.

First, the jsonwebtokenmodule is imported src/index.jsfiles:

const jwt = require('jsonwebtoken'); 

After importing the module, we can add a function to verify the validity of the token:

// get the user info from a JWT
const getUser = token => {
  if (token) {
    try {
      // return the user information from the token
      return jwt.verify(token, process.env.JWT_SECRET);
    } catch (err) {
      // if there's a problem with the token, throw an error
      throw new Error('Session invalid');
    }
  }
};

Now, in each GraphQLrequest, we will get the token from the request header, try to verify the validity of the token, and add user information to the context. After this is done, every GraphQLresolver can access the user we stored in the token ID.

// Apollo Server setup
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => {
    // get the user token from the headers
    const token = req.headers.authorization;
    // try to retrieve a user with the token
    const user = getUser(token);
    // for now, let's log the user to the console:
    console.log(user);
    // add the db models and the user to the context
    return { models, user };
  }
}); 

Although we have not yet performed user interaction, we can GraphQL Playgroundtest the user context in it. In GraphQL Playground UIthe lower left corner of the, there is HTTP Headera space marked as . That portion of the user interface, we can add comprise a JWTheader, the header is through signUpor signInmodify the return, as shown in (FIG. 7- 3):

{
  "Authorization": "<YOUR_JWT>"
} 

Figure 7-3 . GraphQL PlaygroundAuthorization header in

We can GraphQL Playgroundtest this authorization header by passing it along with any query or modification in. To this end, we will write a simple query notes, and include Authorizationthe header (Figure 7- 4).

query {
  notes {
    id
  }
}

Figure 7-4 . GraphQL PlaygroundAuthorization headers and queries in

If our authentication is successful, we should see an IDobject containing the user recorded in the output of the terminal application, as shown in Figure 7- 5.

Figure 7-5 . console.logUser object in terminal output

After completing all these steps, we can now APIauthenticate the user in ours .

in conclusion

The user account creation and login process can be confusing and overwhelming, but by doing it one by one, we can APIimplement a stable and secure authentication process in ours . In this chapter, we created the registration and login user process. These are only a small part of the account management ecosystem, but will provide us with a stable foundation. In the next chapter, we will APIimplement a user-specific interaction in which assigns ownership to notes and activities in the application.

If there is any inadequate understanding, please correct me. If you think it's okay, please like to collect or share it, hoping to help more people.

Guess you like

Origin blog.csdn.net/code_maomao/article/details/110110160