プログラム関係の備忘録。技術系記事1000記事以上を目指すブログ

【実践編】Reactとexpress。Json Web Tokenで認証機能を実装

はじめに

今回すること

前回は説明が長くなってしまい書けなかった部分を書いていきます。
具体的には、以下

①認証処理をモジュール化して使いやすくする。
②React側からアクセスしてみる

Json Web Tokenって?というかたはまず前回の記事を読んでみてください。
【解説編】node.js expressを使ったAPIサーバーでJson Web Tokenの使い方

モジュール化して使いやすくする

app.js

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');

var login = require('./routes/login');
var user = require('./routes/user');

var app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/login', login);
app.use('/user', user)

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

上記はexpressジェネレーターで生成されたapp.jsに少し追記したもの。

var login = require('./routes/login');
var user = require('./routes/user');

app.use('/login', login);
app.use('/user', user)

こんな感じで/***にアクセスしたときの処理を別ファイルに切り出しています。
使うのは「./routes/user」、「./routes/login」の2ファイルです。

login.js

var express = require('express');
var router = express.Router();
var connection = require('../../connection.js');
var cors = require( 'cors' );
var secretkey = require('../../setting/secretKey.js');
var jwt = require( 'jsonwebtoken' );

router.use( cors() );

router.post('/', (req, res) => {
  let param = {
    account: req.body.account,
    password: req.body.password
  }

  if(param.account === '' || param.password === ''){
    res.sendStatus(403)
    return
  }

  sql  = " SELECT ";
  sql += "  ID ";
  sql += " from USER ";
  sql += " WHERE LOGIN_ID = ? ";
  sql += " AND PASSWORD = ? ";

  var params = [param.account,param.password]
  connection.getConnection(function(err, connection){
    connection.query(sql, params, function (error, results, fields) {

      if (error) console.log("disconnect",error);

      if(results.length === 0){
        res.json({
          'token':''
        })
        return
      }
  
      jwt.sign({user: results}, secretkey, {expiresIn: '1h'}, (err, token) => {
        res.json({
          token,results
        })
      });

      connection.release();
    });
  });
})

module.exports = router;

ここでは以下の2ファイルを読み込みます

// MySQL接続用の文字列情報
var connection = require('../../setting/connection.js');

// jwtのsecretkeyを別ファイルに定義したもの
var secretkey = require('../../setting/secretKey.js');

それぞれのファイルの中身はこのようになります。
※実環境と合わせて随所変更してください

connection.js

// mysqlのモジュールを読み込み
var mysql = require('mysql');

// dbコンテナの接続設定
var config = mysql.createPool({
    host: 'IPアドレスをここに',
    user: 'ユーザー名',
    password: 'パスワード',
    database : 'データベース名'
});

module.exports = config;

secretKey.js

var secretKey = 'シークレットキーの文字列をここに'

module.exports = secretKey;

user.js

var express = require('express');
var router = express.Router();
var connection = require('../../setting/connection.js');
var cors = require( 'cors' );
var secretkey = require('../../setting/secretKey.js');
var jwt = require( 'jsonwebtoken' );
var verifyToken = require('../../setting/makeJwt.js');

router.use( cors() );

router.post('/', verifyToken, (req, res) => {
  jwt.verify(req.token, secretkey, (err, authData) => {
    if(err){
      res.sendStatus(403)
    } else {
      
      sql  = " SELECT ";
      sql += "  * ";
      sql += " from USER ";
      sql += " WHERE ID = ? ";
      
      let params = [authData.user[0].ID]

      connection.getConnection(function(err, connection){
        connection.query(sql, params, function (error, results, fields) {
    
          if (error) console.log("disconnect",error);
            res.send({
              results
          })
    
          connection.release();
        });
      });
      
    }
  })
})

module.exports = router;

このファイルでは以下のファイルを別ファイルに切り出しています。

// login.jsでも使ったMySQL接続文字列
var connection = require('../../setting/connection.js');

// 同じく先ほども使った秘密鍵の情報
var secretkey = require('../../setting/secretKey.js');

// パッケージ読み込み
var jwt = require( 'jsonwebtoken' );

// 前回の記事でトークンのチェック処理
var verifyToken = require('../../setting/Jwt.js');

Jwt.jsの中身は前回app.jsで書いた内容とほぼ変わりません

function verifyToken(req, res, next) {
    const bearerHeader = req.headers['authorization']
    if(typeof bearerHeader !== 'undefined'){
        const bearer = bearerHeader.split(' ')
        const bearerToken = bearer[1]
        req.token = bearerToken
        next()
    } else {
        res.sendStatus(403)
    }
}

module.exports = verifyToken;

これでテスト用DBに格納したUSERテーブルからIDを暗号化し、/userにアクセスした際にIDを複合して自分の情報を取り出す処理ができました。

React側からアクセスしてみる

React側ではaxiosを使ってAPIを叩きます。
stateにアカウントID、パスワードが格納されている前提で、以下のようになります。

ログイン処理を実行するコンポーネント

    axios.post('http://*****/login', {
      account: this.state.account,
      password: this.state.password
    })
    .then(response => {
        if(response.statusText !== 'OK'){
          alert('データを取得できませんでした。')
          return          
        }
        if(response.data.token === ''){
          alert('ログインに失敗しました。')
          return          
        }
        localStorage.setItem( 'token', response.data.token );
    }).catch(err => {
        console.log('err:', err);
    });

リクエストしたアカウント、パスワードでDBにデータがない場合にはAPI側でtokenを空にして返すようにしているので、
結果が空だったらログイン失敗、空でなければトークンをストレージにセットしています。

続いて、ログイン状態でPOSTのリクエストを投げるときの処理を書いていきます。

    axios.post('http://*****/user')
    .then(response => {
        if(response.statusText !== 'OK'){
          alert('データを取得できませんでした。')
          return          
        }
        if(response.data.results.length === 0){
          alert('データを取得できません。')
          return          
        }
        let useInfo = response.data.results[0]

        this.setState({ userName: useInfo})

    }).catch(err => {
        console.log('err:', err);
    });

トークンが付与されていてデータが返ってきた場合にはresponse.data.resultsに結果が入るので、取得できれば正常、できない場合はエラーとして書いています。

ここで本来リクエストヘッダーにトークンを付与する処理が必要なのですが、毎回付与するのもめんどうなので
認証が必要な親コンポーネントの部分などで以下のように定義をしておきます。
こうすることで、デフォルトでヘッダーにトークンを付与した状態でリクエストを投げてくれます。

let token = localStorage.getItem('token');
axios.defaults.headers.common = {'Authorization': `bearer ` + token}

さいごに

これで一通り、ReactとAPIを使った処理に認証をかける実装が完成しました。
エラー時の処理や細かい部分でまだ足りないところはありますが、ここまでできれば「かなり理解が進んだ」&「後は動かしながら処理を追加していけば良い」くらいまでには来られることができたかなと思います。

最後まで読んでいただきありがとうございました。