著者:しょっさん
プログラミング言語「JavaScript」の実行環境「Node.js」と「Express」フレームワークを使って、基本となるWebアプリの開発手法を習得しましょう。最終回は、サンプルの「蔵書管理アプリケーション」の課題を解決し、「SPA」(Single Page Application)として実装します。
シェルスクリプトマガジン Vol.59は以下のリンク先でご購入できます。![]()
図2 認証時にアクセストークンを発行する部分のプログラム
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へのアクセスしたときのアクセストークンの検査部分のプログラム
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 カスタムコールバックを作成している部分のプログラム
// 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冊の本の情報を取得する
_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形式に変更する
// 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 テストコードを変更する
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);
});
});
});