著者:しょっさん
プログラミング言語「JavaScript」の実行環境「Node.js」と「Express」フレームワークを使って、基本となるWebアプリの開発手法を習得しましょう。最終回は、サンプルの「蔵書管理アプリケーション」の課題を解決し、「SPA」(Single Page Application)として実装します。
シェルスクリプトマガジン Vol.59は以下のリンク先でご購入できます。
図2 認証時にアクセストークンを発行する部分のプログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
var express = require('express'); var router = express.Router(); const jwt = require('jsonwebtoken'); const models = require('../models'), User = models.user; var app = express(); // パスワードハッシュ化 const hashPassword = (password, salt) => { if (password) { var bcrypt = require('bcrypt'); return bcrypt.hashSync(password, salt); } else { return null; } }; // ログイン処理 router.post('/', (req, res) => { const username = req.body.email; const password = req.body.password; User.findOne({ where: { email: username } }) .then(user => { if (!user) { res .status(401) .json({ errors: { message: 'Incorrect email.' } }); } else if (hashPassword(password, user.salt) !== user.password) { res .status(401) .json({ errors: { message: 'Incorrect password.' } }); } else { const opts = { issuer: process.env.ISSUER, audience: process.env.AUDIENCE, expiresIn: process.env.EXPIRES, }; const secret = process.env.SECRET; res .status(200) .json({ 'token': jwt.sign({ id: user.id }, secret, opts) }); } }); }); |
図3 URLへのアクセスしたときのアクセストークンの検査部分のプログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
const passport = require('passport'); const passportJWT = require('passport-jwt'); const ExtractJWT = passportJWT.ExtractJwt; const JWTStrategy = passportJWT.Strategy; // using authentication strategy passport.use(new JWTStrategy( { jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), issuer: process.env.ISSUER, audience: process.env.AUDIENCE, secretOrKey: process.env.SECRET }, (jwt_payload, done) => { User.findOne({ where: { id: jwt_payload.id } }) .then(user => { if (user) { done(null, user); } else { done(null, false); } }) .catch(err => { return done(err, false); }); } )); |
図4 カスタムコールバックを作成している部分のプログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// passport-jwt: custom callback // Tokenを持っていなかったり、Invarid Tokenの場合、Json形式で返答しなかったりするので、カスタムコールバックで準備する必要がある const jwt = (req, res, next) => { passport.authenticate('jwt', { session: false }, (err, user, info) => { if (err) { return next(err); } if (!user) { return res.status(401).json({ 'errors': { 'message': info || 'user unknown' } }).end(); } req.user = user; next(); })(req, res, next); }; // ルーティング: 認証が必要なURIには、jwt関数コールを追記するだけでよい app.use('/api/auth', auth); app.use('/api/books', jwt, books); |
図5 表現に合わせてJSON形式で返却する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// 1冊の本の情報を取得する _get_book(book_id) { return libraries.findOne({ where: { user_id: this._user_id, id: book_id }, include: [{ model: comments, required: false }] }); } // 1冊の本の詳細を表示する find(req, res) { this._get_book(req.params.id) .then(result => { res .status(200) .json(result.get()); }).catch(() => { res .status(200) .json({}); }); } |
図6 エラー画面をJSON形式に変更する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// catch 404 and forward to error handler app.use((req, res, next) => { var err = new Error('Not Found'); err.status = 404; next(err); }); // error handler app.use((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.json({ success: false, errors: { message: err.message } }); }); |
図7 テストコードを変更する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
describe('with Login', () => { // store a jwt token let jwt_token; beforeAll((done) => { request(app) .post('/api/auth/') .send(userCredentials) .end((err, res) => { jwt_token = res.body.token; done(); }); }); describe('GET /api/books/', () => { it('respond with REST', (done) => { request(app) .get('/api/books/') .set('Accept', 'application/json') .set('Authorization', `Bearer ${jwt_token}`) .expect('Content-Type', /json/) .expect(200, done); }); }); }); |