Translation | " JavaScript
Everywhere
" 7
Chapter 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\_maomao
welcome 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 Web
same process that we follow every time we sign up for the app.
In this chapter, we will learn how to build a GraphQL
modification 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:
-
The user enters the email address, user name, and password into the user interface (
UI
), for exampleGraphQL
Playground
,Web
an app or mobile app. -
The user interface uses the user information to send the
GraphQL
request to our server. -
The server encrypts the password and stores the user information in the database.
-
The server returns a token to the user interface, which contains the user's
ID
. -
UI
Store 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:
-
Users
UI
enter their email or username and password in the fields. -
UI
This information will be usedGraphQL
to send requests to our servers. -
The server decrypts the password stored in the database and compares it with the password entered by the user.
-
If the passwords match, the server will return a token to the user interface, which contains the user's
ID
. -
UI
Store 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 API
parts 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
Spectrum
community.
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.
bcrypt
Based on the blowfish password, it is usually used in a series of network frameworks. In Node.js
development, we can use the bcrypt
module for password hashing and salting.
In our application code, we will need to use bcrypt
the 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 bcrypt
integrate 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
@ rty99
and 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.7oaMwVga54bWG
The 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 bcrypt
the compare
method:
// 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
Web
Token
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
Web
token ID
securely 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
Web
The 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
Ameans
Verification 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 jsonwebtoken
modules to generate and verify our tokens. To this end, we pass on the information we wish to store and .env
the 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 Web
used 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 JWT
or conversation, which one has a better argument? But I find that it JWT
provides the most flexibility, especially when integrating with non- Web
environments (such as native mobile applications). Although the session can GraphQL
use a good match, but it JWT
still is GraphQL
Foundation
and Apollo
Server
recommended method document.
Through use JWT
, we can safely return the user ID
and 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 GraphQL
and Mongoose
mode, write signUp
and signIn
modify the parser to generate a user token, and token verification request to each server.
User structure
First, we will add the User
type and update the Note
type of author
field, a reference User
to the update GraphQL
mode. To do this, the update src/schema.js
file:
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 API
will be returned as a string token. In order to accomplish this in our mode, we will need src/schema.js
to 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 GraphQL
model has been updated, we also need to update the database module. For this, we will src/models/user.js
create a Mongoose
pattern 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.
unique
true
To create a user database module, src/models/user.js
enter 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.js
to export module:
const Note = require('./note');
const User = require('./user');
const models = {
Note,
User
};
module.exports = models;
Authentication parser
After writing the GraphQL
sum Mongoose
pattern, we can implement the parser so that users can register and log in to our application.
First, we need to .env
add a file environment JWT\_SECRET
variable. The value should be a string without spaces. It will be used to JWT
sign ours , which allows us to verify them when decoding them.
JWT_SECRET=YourPassphrase
After creating this variable, the required packages can import our mutation.js
file. We will use a third-party bcrypt
, jsonwebtoken
, mongoose
and dotenv
package, and import the Apollo
server AuthenticationError
and ForbiddenError
utilities.
In addition, we will import the gravatar
utility 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 signUp
request. 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 bcrypt
module to encrypt user passwords. We will also helper
generate Gravatar
images 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/catch
make 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.js
preparation of the file signUp
requested, 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 signUp
the request. For this, we will write a GraphQL
modification 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
Playground
The signUp
request
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.js
file, 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 signUp
attempting to account request to create signIn
a request:
mutation {
signIn(
username: "BeeBoop",
email: "[email protected]",
password: "NotARobot10010!"
)
}
Similarly, if successful, we should adopt changes JWT
to resolve (Figure 7
- 2
):
{
"data": {
"signIn": "<TOKEN VALUE>"
}
}
Figure 7
-2
. GraphQL
Playground
The signIn
modification
With these two resolvers, users will be able to JWT
register 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
API
returned.
Add user to parser context
Now that the user can use GraphQL
requests 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 HTTP
authorized token with the header mentioned in the request .
We can then HTTP
use JWT\_SECRET
to read the token from the header, decode it using variables, and pass user information and context to each GraphQL
parser. 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 jsonwebtoken
module is imported src/index.js
files:
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 GraphQL
request, 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 GraphQL
resolver 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
Playground
test the user context in it. In GraphQL
Playground
UI
the lower left corner of the, there is HTTP
Header
a space marked as . That portion of the user interface, we can add comprise a JWT
header, the header is through signUp
or signIn
modify the return, as shown in (FIG. 7
- 3
):
{
"Authorization": "<YOUR_JWT>"
}
Figure 7
-3
. GraphQL
Playground
Authorization header in
We can GraphQL
Playground
test 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 Authorization
the header (Figure 7
- 4
).
query {
notes {
id
}
}
Figure 7
-4
. GraphQL
Playground
Authorization headers and queries in
If our authentication is successful, we should see an ID
object containing the user recorded in the output of the terminal application, as shown in Figure 7
- 5
.
Figure 7
-5
. console.log
User object in terminal output
After completing all these steps, we can now API
authenticate 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 API
implement 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 API
implement 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.