188
162

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

node.js/expressでユーザ認証with JWT

Last updated at Posted at 2016-02-12

はじめに

node + expressで以下のようなことをしてみます

  • mongoDBに保存しているname/passでユーザ認証
  • 認証OKならJWT形式のtokenを発行して返却
  • JWTトークンを使って認証要のAPIにアクセス

これらをform認証でなく、CUrl等を利用してできるようにします。

このサイトにしたがって実施してみます

必要なもの

  • node
  • npm
  • POSTman(api検証用のchrome extention)
  • mongoDB

サーバに実装するもの

  • secureとsecure外のURL
  • nameとpasswordによるユーザ認証
    • 認証後にtokenを返却
  • ユーザは取得したtokenを保存、全リクエストに付与
  • tokenを検証、OKであればJSONで情報を返却

mongoDBのインストール(mac)


# install
brew install mongodb

# mongoDBを自動起動
ln -sfv /usr/local/opt/mongodb/*.plist ~/Library/LaunchAgents
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mongodb.plist

projectの作成

mkdir server.oauth
cd server.oauth
mkdir -p app/models
touch app/models/user.js
touch config.js
touch package.json
touch server.js
- app/
----- models/
---------- user.js
- config.js
- package.json
- server.js

package.json

{
  "name": "server.oauth",
  "main": "server.js"
}

依存ライブラリのinstall

npm install express body-parser morgan mongoose jsonwebtoken --save
  • express is ポピュラーなNode Framework
  • mongoose is MongoDB用のO/Rmapper
  • morgan is ログをコンソールに出力する為に利用
  • body-parser is postされたパラメータのparser
  • jsonwebtoken is JWTの作成・検証用library

User Model(app/models/user.js)

// get mongoose.Schema
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

// make user model and export
module.exports = mongoose.model('User', new Schema({
  name: String,
  password: String,
  admin: Boolean
}));

Config File(config.js)

module.exports = {
  'secret': 'oauthServerSampleSecret',
  'database': 'mongodb://localhost/server_oauth'
}
  • secret : JWTの作成と検証に使用する文字列、任意に変更する
  • database : mongoDBの接続URI

Main file(server.js)

  • ひとまず初期設定と起動する最低限のみ記述
// =======================
// get instance we need
// =======================
var express         = require('express');
var app                = express();
var bodyParser    = require('body-parser');
var morgan          = require('morgan');
var mongoose      = require('mongoose');
var jwt                   = require('jsonwebtoken');

var config              = require('./config');
var User                = require('./app/models/user');

// =======================
// configuration
// =======================
// server setting
var port = process.env.PORT || 8080;

// connect databse
mongoose.connect(config.database);

// application variables
app.set('superSecret', config.secret);

// config for body-parser
app.use(bodyParser.urlencoded({ extended: false}));
app.use(bodyParser.json());

// log request
app.use(morgan('dev'));

// =======================
// routes
// =======================
app.get('/', function(req, res) {
  res.send('Hello! The API is at http://localhost:' + port + '/api');
});



// =======================
// start the server
// =======================
app.listen(port);
console.log('started http://localhost:' + port + '/');


おためし起動

node server.js
  • 起動したらブラウザを開き、http://localhost:8080/ にアクセス
    • 成功すればHello...の文字が表示される
  • 停止するにはコマンドラインで control+c

ソース変更を反映するには再起動が必要。
めんどくさい場合、npm install -g nodemon をして
nodemon server.js としておくと変更を検知して自動再起動してくれる

テストユーザ作成用URLの作成(server.js)

// for create test user to db
app.get('/setup', function(req, res) {
  var demo = new User({
    name: 'demouser',
    password: 'password',   // TODO: encrypt password
    admin: true
  });

  demo.save(function(err) {
    if (err) throw err;

    console.log('User saved successfully');
    res.json({ success: true});
  });

});

mongoDBに登録されているか確認

mongo
use server_oauth
db.users.find()

mongodbの操作はここを参照
database一覧を見るには show dbs
table一覧を見るには show collections

APIを作成する(server.js)

  • API用のURLを作成
    • API用のURLはexpress.Routerを使ってグルーピングして定義する
  • ユーザの一覧を取得するAPIを作成
// API ROUTES ================

var apiRoutes = express.Router();

// GET(http://localhost:8080/api/)
apiRoutes.get('/', function(req, res) {
  res.json({ message: 'Welcome to API routing'});
});

// GET(http://localhost:8080/api/users)
apiRoutes.get('/users', function(req, res) {
  User.find({}, function(err, users) {
    if (err) throw err;
    res.json(users);
  });
});

// apply the routes to our application(prefix /api)
app.use('/api', apiRoutes);

Authenticating and Creating a Token

// POST(http://localhost:8080/api/authenticate)
apiRoutes.post('/authenticate', function(req, res) {

  // find db by posted name
  User.findOne({
    name: req.body.name
  }, function(err, user) {
    if (err) throw err;

    // validation
    if (!user) {
      res.json({
        success: false,
        message: 'Authentication failed. User not found.'
      });
      return;
    }

    if (user.password != req.body.password) {
      res.json({
        success: false,
        message: 'Authentication failed. Wrong password.'
      });
      return;
    }

    // when valid -> create token
    var token = jwt.sign(user, app.get('superSecret'), {
      expiresIn: '24h'
    });

    res.json({
      success: true,
      message: 'Authentication successfully finished.',
      token: token
    });

  });

});

Authenticateのテスト

  • POSTmanを使って試す
    • methodをPOSTにする
    • Bodyタブを開き、x-www-form-urlencodedを選択
    • Key, valueを以下のようにセット
      • Key: name Value: demouser
      • Key: password Value: password
    • うまくいけばtokenが返却されていることがわかる

Kobito.4BNXEm.png

認証が必要なページをprotectし、tokenが合致すれば通す

  • 認証Filterはexpress.Router().useで作成する
  • Filterを定義した以降に定義したURLはFilter通過後に動作する
    • コードの書く順序 が大事
// API ROUTES

var apiRoutes = express.Router();

// non secure api --------

// POST(http://localhost:8080/api/authenticate)
...


// Authentification Filter
apiRoutes.use(function(req, res, next) {

  // get token from body:token or query:token of Http Header:x-access-token
  var token = req.body.token || req.query.token || req.headers['x-access-token'];

  // validate token
  if (!token) {
    return res.status(403).send({
      success: false,
      message: 'No token provided.'
    });
  }

  jwt.verify(token, app.get('superSecret'), function(err, decoded) {
    if (err) {
      return res.json({
        success: false,
        message: 'Invalid token'
      });
    }

    // if token valid -> save token to request for use in other routes
    req.decoded = decoded;
    next();

  });

});

// secure api --------

// GET(http://localhost:8080/api/)
...

// GET(http://localhost:8080/api/users)
...

// apply the routes to our application(prefix /api)
app.use('/api', apiRoutes);


tokenを使ってAPIが使えることを検証

  • tokenを使わず、apiをコールするとエラーになる

Kobito.MpW1qa.png

  • POSTmanを使って、正しいname/passwordをPOST
  • 取得できたtokenをコピー

Kobito.O6Xf7O.png

  • POSTmanでtokenを含めた形でAPIをcall

Kobito.onSu9R.png

188
162
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
188
162

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?