[MERN②]User API Routes & JWT Authentication

~~~~~~~~~~ (Contents) MERN ~~~~~~~~~~~
[MERN①] Express & MongoDB Setup
https://qiita.com/niyomong/private/3281af84486876f897f7
[MERN②]User API Routes & JWT Authentication
https://qiita.com/niyomong/private/c11616ff7b64925f9a2b
[MERN③] Profile API Routes
https://qiita.com/niyomong/private/8cff4e6fa0e81b92cb49
[MERN④] Post API
https://qiita.com/niyomong/private/3ce66f15375ad04b8989
[MERN⑤] Getting Started With React & The Frontend
https://qiita.com/niyomong/private/a5759e2fb89c9f222b6b
[MERN⑥] Redux Setup & Alerts
https://qiita.com/niyomong/private/074c27259924c7fd306b
[MERN⑦] React User Authentication
https://qiita.com/niyomong/private/37151784671eff3b92b6
[MERN⑧] Dashboard & Profile Management
https://qiita.com/niyomong/private/ab7e5da1b1983a226aca
[MERN⑨] Profile Display
https://qiita.com/niyomong/private/42426135e959c7844dcb
[MERN⑩] Posts & Comments
https://qiita.com/niyomong/private/19c78aea482b734c3cf5
[MERN11] デプロイ
https://qiita.com/niyomong/private/150f9000ce51548134ad
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


##1. User Modelを作成

Modelフォルダ作成
Userモデルファイル作成

```models/User.js
const mongoose = require('mongoose');

const UserSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
  },
  email: {
    type: String,
    required: true,
    unique: true,
  },
  password: {
    type: String,
    required: true,
  },
  avatar: {
    type: String,
  },
  date: {
    type: Date,
    default: Date.now,
  },
});

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

##2. リクエスト & Bodyバリデーション


```server.js
...
connectDB();

+ // Init Middleware
+ app.use(express.json({ extended: false }));

app.get('/', (req, res) => res.send('API Running'));
...
```


###express-validator
https://express-validator.github.io/docs/

```routes/api/users.js
const express = require('express');
const router = express.Router();
const { body, validationResult } = require('express-validator');

+ // @route  POST api/users
// @desc   Register user
// @access Public
router.post(
  '/',
+   [
+     body('name', 'Name is required').not().isEmpty(),
+     body('email', 'Please include a valid email').isEmail(),
+     body(
+       'password',
+       'Please enter a password with 6 or more characters'
+     ).isLength({ min: 6 }),
+   ],
  (req, res) => {
+     const errors = validationResult(req);
+     if (!errors.isEmpty()) {
+       return res.status(400).json({ errors: errors.array() });
+     }

    res.send('User route');
  }
);

module.exports = router;

```

###POSTMANによるチェック
（nameのみ入力の場合）-> "400 Bad Request"（失敗の返答）
![スクリーンショ!ット 2020-09-06 0.12.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294859/2daa591a-117d-8e58-63ed-7d39fd87ca11.png)

（name、 email、６桁passすべて入力した場合）-> "200 OK"（成功の返答）
![スクリーンショット 2020-09-06 0.15.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294859/774e0db5-90ed-adbd-9729-a106358a25dc.png)


##User Registration

-> gravatarを追加
-> bcryptを追加

```routes/api/user.js
const express = require('express');
const router = express.Router();
+ const gravatar = require('gravatar');
+ const bcrypt = require('bcryptjs');
const { body, validationResult } = require('express-validator');

const User = require('../../models/User');

// @route  POST api/users
// @desc   Register user
// @access Public
router.post(
  '/',
  [
    body('name', 'Name is required').not().isEmpty(),
    body('email', 'Please include a valid email').isEmail(),
    body(
      'password',
      'Please enter a password with 6 or more characters'
    ).isLength({ min: 6 }),
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

+     const { name, email, password } = req.body;
+     try {
+       let user = await User.findOne({ email });

+      if (user) {
+         // See if user exists
+         return res.status(400).json({ erros: [{ msg: 'User already exists' }] });
+       }
+       // Get users gravatar
+       const avatar = gravatar.url(email, {
+         s: '200',
+         r: 'pg',
+         d: 'm',
+       });
+       user = new User({ name, email, avatar, password });
+       // Encrypt password
+       const salt = await bcrypt.genSalt(10);
+       user.password = await bcrypt.hash(password, salt);
+       await user.save();
+       // Return jsonwebtoken
+       res.send('User registered');
+     } catch (err) {
+       console.error(err.message);
+       res.status(500).send('Server error');
+     }
+   }
);

module.exports = router;
```

###POSTMANでPost
![スクリーンショット 2020-09-06 0.42.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294859/6be54f38-aec3-24d9-cfcd-ad0a44fcfa5c.png)

###MongoDB Atlasに格納された！
![スクリーンショット 2020-09-06 0.48.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294859/55dbd629-0774-b0bb-b3fd-ccbac77c905c.png)


###登録した後、再度Postをすると

![スクリーンショット 2020-09-06 0.47.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294859/bd2274ca-66cd-c953-6111-d7e0acf05375.png)


##3. JWT (jsonWebToken)を実行

JWTの説明：https://github.com/auth0/node-jsonwebtoken
-> jwtを設置
-> configを設置

```routes/api/users.js
...
const bcrypt = require('bcryptjs');
+ const jwt = require('jsonwebtoken');
+ const config = require('config');
const { body, validationResult } = require('express-validator');

...
      // Encrypt password
      const salt = await bcrypt.genSalt(10);
      user.password = await bcrypt.hash(password, salt);
      await user.save();

      // Return jsonwebtoken
+       const payload = {
+         user: {
+           id: user.id,
+         },
+       };

+       jwt.sign(
+         payload,
+         config.get('jwtSecret'),
+         { expiresIn: 360000 },
+         (err, token) => {
+           if (err) throw err;
+           res.json({ token });
+         }
      );
    } catch (err) {
      console.error(err.message);
      res.status(500).send('Server error');
...
```

mongoDB -> _id
mongoose -> id

```config/default.json
{
  "mongoURI": "xxx",
+  "jwtSecret": "xxxxxtoken"
}

```

https://jwt.io/#encoded-jwt


##3. Authミドルウェアをカスタマイズ & JWT証明
middleware/auth.jsファイル作成

```middleware/auth.js
const jwt = require('jsonwebtoken');
const config = require('config');

module.exports = function (req, res, next) {
  //Get token from header
  const token = req.header('x-auth-token');

  // Check if not token
  if (!token) {
    return res.status(401).json({ msg: 'No token, authorization denied' });
  }

  //Verify token
  try {
    const decoded = jwt.verify(token, config.get('jwtSecret'));

    req.user = decoded.user;
    next();
  } catch (err) {
    res.status(401).json({ msg: 'Token is not valid' });
  }
};

```

```routes/api/auth.js
const express = require('express');
const router = express.Router();
+ const auth = require('../../middleware/auth');

+ const User = require('../../models/User');

// @route  GET api/auth
// @desc   Test route
// @access Public
+ router.get('/', auth, async (req, res) => {
+   try {
+     const user = await User.findById(req.user.id).select('-password'); //passwordは除くUserデータを取得
+     res.json(user);
+   } catch (err) {
+     console.error(err.message);
+     res.status(500).send('Server Error');
+   }
});

module.exports = router;

```


POSTMAN
Headersに
-> KEY: x-auth-token
-> VALUE: POST /api/usersした時にJWTでレスのあったTokenをペースト

![スクリーンショット 2020-09-06 16.30.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294859/ae0ee6e5-662e-d4f1-2617-48669f23b87c.png)



##4. ユーザー認証 & ログイン

```routes/api/auth.js
const express = require('express');
const router = express.Router();
+ const bcrypt = require('bcryptjs');
const auth = require('../../middleware/auth');
+ const jwt = require('jsonwebtoken');
+ const config = require('config');
+ const { body, validationResult } = require('express-validator');

const User = require('../../models/User');

// @route  GET api/auth
// @desc   Test route
// @access Public
router.get('/', auth, async (req, res) => {
   try {
    const user = await User.findById(req.user.id).select('-password');
    res.json(user);
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server Error');
  }
});


//以下、routes/api/user.jsからコピペして修正

+ // @route  POST api/auth
+ // @desc   Authenticate user & get token
+ // @access Public
+ router.post(
+   '/',
+   [
+     body('email', 'Please include a valid email').isEmail(),
+     body('password', 'Password is required').exists(),
+   ],
+   async (req, res) => {
+     const errors = validationResult(req);
+     if (!errors.isEmpty()) {
+       return res.status(400).json({ errors: errors.array() });
+     }

+     const { email, password } = req.body;

+     try {
+       let user = await User.findOne({ email });

+       if (!user) {
+         // See if user exists
+         return res
+           .status(400)
+           .json({ erros: [{ msg: 'Invalid Credentials' }] });
+       }

+       const isMatch = await bcrypt.compare(password, user.password);

+       if (!isMatch) {
+         return res
+           .status(400)
+           .json({ erros: [{ msg: 'Invalid Credentials' }] });
+       }

+       // Return jsonwebtoken
+       const payload = {
+         user: {
+           id: user.id,
+         },
+       };

+       jwt.sign(
+         payload,
+         config.get('jwtSecret'),
+         { expiresIn: 360000 },
+         (err, token) => {
+           if (err) throw err;
+           res.json({ token });
+         }
+       );
+     } catch (err) {
+       console.error(err.message);
+       res.status(500).send('Server error');
+     }
+   }
);

module.exports = router;

```

###POSTMANで登録済みの"email"と"password"をPOSTした場合
![スクリーンショット 2020-09-06 16.51.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294859/8110f37f-e0b0-20e7-dd3a-2ea3144614e2.png)

###誤った"email"と"password"をPOSTした場合
![スクリーンショット 2020-09-06 16.52.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294859/a5638388-2bee-34df-261f-47b27e724d19.png)