九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、もしかすると感の鋭い方はうっすらお気づきかもしれませんが、ここのところ公開している以下の記事はある「密かな野望」を狙ったものでした。
元々これらの記事は以下の記事を延長して作ったものですが、実はその「おわりに」で軽い気持ちで書いたことが「密かな野望」のきっかけになっています。
この記事で、2020年1月15日現在の私はこう書きました。
パッケージの initialize() で必要最低限はすべて実行してくれたらうれしいなー、なんてナマケモノ・プログラマーの私は思ってしまいました(笑)
特に重要な部分はここです。
すべて実行してくれたらうれしいなー
つまり、Laravel
のように以下5つの機能を一気に使えるようになったら楽でいいよねー、といういかにもナマケモノらしい発想です。
- ログイン
- ユーザー登録
- パスワード再発行
- 自動ログイン(次回から省略)
- CSRF対策
正直なところ、パッケージをつくるのは労力がいるのでどうしようか少し考えていたのですが、あれば便利ですし、何より作ってみるのが面白そうだったので、昨年一斉を風靡した言葉をパロディしながら開発をはじめてみました。
『ナマケモノ、動きます。』
ということで、今回はExpress
に総合的なログイン機能を追加する「Express-Ticket
」公開記念として、使い方をご紹介したいと思います。
【追記:2023.2.23】
(このパッケージで使っている)Sequlize
本体に脆弱性があるようで公開を中止しました。メンテナンスする時間もないので。スイマセン❗)m(_ _)m
目次
名前の由来
元祖のExpress
が、どの意味で名付けられたか分かりませんが、この単語には「急行列車」という意味も含まれているので、その列車に乗るためのチケットという意味で「Express-Ticket」としました。つまり、乗車するための切符(認証)のイメージですね。
パッケージへのリンク
Express-Ticket
は、以下2つの場所で公開しています。
日本語化してます
Express-Ticket
は、i18n
パッケージを使っていて、バリデーションのメッセージは日本語と英語の2種類を用意しました。なお、ご自身で他の言語も追加できますし、元々のメッセージを変更することもできます。
パッケージのインストール
npm
コマンド一発でインストールが完了します。(なお、Express-Ticketが必要とする依存パッケージも一緒にインストールされます)
npm i --save express-ticket
準備する
Sequlizeで初期化
Express-Ticket
は、DB操作にSequlize
というパッケージを使っています。(Sequelize
については、以下の記事をご覧ください)
そのため、まずはじめに以下のコマンドでDBの準備をします。
npx sequelize init
このコマンドを実行すると、「/config/config.json」というファイルが作成されるので中身を開いてご自身のDB接続情報を書き込んでください。
{ "development": { "username": "(DBユーザー)", "password": "(DBパスワード)", "database": "(DB名)", "host": "127.0.0.1", "dialect": "mysql", "operatorsAliases": 0, "timezone": "+09:00" }, // 以下省略 }
なお、データベースにMySQL
を使う場合は以下のコマンドでMySQL用パッケージをインストールしますが、別のSQLを使う場合はこちらのページで確認してください。
npm i --save mysql2
Express-TicketのファイルをPublishする
次に、Express-Ticket
の中に含まれているファイルをPublish(コピー)します。
以下のコマンドを実行してください。
npx ticket publish
実行が完了すると、以下のファイルが作成されることになります。
- config/*
- locales/*
- migrations/*
- models/*
- routes/*
- views/auth/*
マイグレーションする
作成されたファイルの中に以下2つのテーブルを作成するマイグレーションが含まれています。
- Users
- PasswordResets
以下のコマンドを実行してテーブルを作成してください。
npx sequelize db:migrate
これで、以下のようにテーブルが作成されます。
設定ファイル
Express-Ticket
の設定ファイルは、/config
フォルダ内に設置されていますが、この中の「トップURL」だけは必ず変更しておいてください。
初期値では、http://example.test
になっています。
const appConfig = { top: 'http://example.test', // ←ここ key: 'YOUR-SECRET-KEY' }; module.exports = appConfig;
※なお、開発環境と本番環境を切り替えるためには、この中でdotenvを使うことをおすすめします。
使い方
では、実際にExpress
内での使い方です。
const express = require('express'); const app = express(); const ExpressTicket = require('express-ticket'); const ticket = new ExpressTicket(); ticket.routes(app, { verify: true, // 初期値: false csrf: true, // 初期値: true locale: 'ja' // もしくは /config/locale.js で設定してください }); app.listen(5000, () => { console.log('Lisening...'); });
これで、以下のようなページが使えるようになります。
(はい、思いっきりLaravel
インスパイアです😊✨)
ログインページ
ユーザー登録ページ
パスワード再設定
なお、もちろんこれらの表示はご自身で変更できます。
Viewファイルは、/views/auth
フォルダに入っています。
また、エラーメッセージは日本語化されます。
テンプレート・エンジンについて
Express-Ticket
は内部的にmustache
を使ってテンプレート処理をしていますが、皆さんはこれに合わせる必要はありません。
例えば、Pug
でテンプレート処理する場合は以下のように設定します。
app.set('view engine', 'pug'); app.set('views', __dirname + '/views');
そして、以下のように通常通りレンダリングすることができます。
app.get('/home', (req, res) => { res.render('/home'); });
なお、user
などのデータはres.locals
を通してすでにバインディングされていますので、パラメータをセットする必要はありません。
ミドルウェアについて
ログインしてる/してないで表示を切り替える
例えば、mustache
を使ってログインしている時と、していない時を切り替えてみましょう。(user
は自動的にバインディングされます)
{{ #user }} ログインしてます。 {{ /user }} {{ ^user }} ログインしてません。 {{ /user }}
ログインしている人だけアクセスできるようにする
例えば、ログインしていたら見ることができ、そうでない場合は強制的にリダイレクトするという場合を見てみましょう。
この場合、ExpressTicketMiddleware
を使います。
const ExpressTicketMiddleware = require('express-ticket/lib/express-ticket-middleware'); const expressTicketMiddleware = new ExpressTicketMiddleware(); const authMiddleware = expressTicketMiddleware.make(); router.get('/only_login_user', authMiddleware, (req, res) => { res.send('ログイン中'); });
これで、ログインしていたら「ログイン中」が表示され、そうでない場合は/login
に強制的にリダイレクトされます。
また、例えばユーザー情報のrole
がadmin
のものだけアクセスできるようにする場合はこうなります。
※ただし、Express−Ticket
には初期状態でrole
は含まれていませんので、マイグレーションで手動で変更してください。
const adminMiddleware = expressTicketMiddleware.make((req, res) => { return (req.user.role === 'admin'); // コールバックがtrueなら表示OK! }); app.get('/only_admin_user', adminMiddleware, (req, res) => { res.send('管理者さん、こんにちは!'); });
依存パッケージのミドルウェアについて
Express-Ticket
は、多数のパッケージを使わせてもらって実装しています。
そして、以下のパッケージが提供するミドルウェアはExpress-Ticket
のクラス内で定義されていますので、重複して呼び出さないよう気をつけてください。
- body parser
- locale
- session
- flash
- cookie parser
- passport
開発に際して
せっかくこういったパッケージを公開することにしたので、開発をした際の問題点とその解決法をまとめておきたいと思います。
パッケージのパス
今回パッケージをつくる際にまず困ったのが、「パス」でした。
例えば、abc
というパッケージをインストールして呼び出すには以下のようにします。
const abc = require('abc');
しかし、独自に開発しているパッケージは/packages/express-ticket
フォルダの中に設置しているので、そのままだと以下のように呼び出す必要があります。
const ExpressTicket = require('./packages/express-ticket/express-ticket.js')
ただ、この形だとパッケージをnpm
で公開した際に、読み込み先のコードで相対パスが歪んでしまう可能性があります。
そこで使ったのが、「シンボリックリンク」でした。
/node_modules
フォルダの中に/pakcage/express-ticket
へのシンボリックリンクリンクをつくり、あたかもそこに存在しているようにして解決をしました。
ルートから独自にデータにアクセスさせる
例えば、以下はExpress
の基本的なルートの使い方です。
router.get('/', (req, res) => { res.render('home'); });
そして、このルートの中でExpress-Ticket
で用意したデータやメソッドにアクセスするにはどうすればいいか最初は悩みました。
なぜなら、このルートの中でインスタンスをつくっても、Express-Ticket
本体のデータにアクセスできないからです。
そこで、これを解決した方法がreq
とres
への独自メソッドの追加でした。
つまり、以下のようにticket
という変数をreq
に追加し、その中に必要なデータを追加したのです。
req.ticket = { __: (key, params = {}) => { return i18n.__(key, params); }, config: this.config, // 以下省略
こうすることで、以下のようにして必要なデータを取得できますし、メソッドだって同じ方法で利用することができます。
router.get('/', (req, res) => { console.log(req.ticket.config); res.render('home'); });
async, await
これはパッケージ開発だけではないとは思いますが、Sequelize
を使ってDBからデータを取得しようとすると、必ずPromise
かasync + await
で取得をしないといけません。
当初は可読性が高いことからasync + await
を使っていたのですが、ある場所でこれを使ってしまうと、その呼び出し元も、さらにその呼び出し元も…となり、延々とasync + await
を強制される結果になったので、最終的にできるだけPromise
を使って処理をするようにしました。(このへんはNode.js
の弱点だと思っています)
CSRF対策の開発
冒頭でも書きましたが、このパッケージはCSRF
対策が使えます。
※ CSRF攻撃については以下をご覧ください。
CSRF(クロスサイト・リクエスト・フォージェリ)攻撃をできるだけ分かりやすく解説
CSRF
対策をするには、一旦作ったランダムな文字列をセッションにいれておいて、データの送信後にその文字列をチェックすることで実装しますが、Node.js
はコードを変更して再読み込みすると、なんとセッションがクリアされてしまうので、コードを変更する度にページをリロードし、再び入力して確認する、という作業を何度もしないといけなかったのがなかなか大変でした😅
解決法はこれといってありません。ゴメンナサイ。m_ _m
コードを探す作業
Express-Ticket
は、もともと私が大好きなLaravel
にインスパイアされてつくったパッケージなので、Laravel
の実装はどうなっているかをひとつひとつチェックする必要がありました。
その中で、いくつもコードを追っていったのですが(phpstorm
はctrl
+ クリックで定義場所まで飛んでくれるのでまだ楽でしたが👍)、ある部分はInterface
で実装していて実際のコードがどこにあるかが分からないということが何度もあり、探す作業に時間がとられるという結果になりました。
ただ、この作業をやったおかげで、よりLaravel
の中身を知ることができスキルアップに繋がったと思います。
やっぱりLaravel
はすごいです😊✨
おわりに
ということで、今回はLaravel
にインスパイアされて開発したログイン総合パッケージExpress-Ticket
のご紹介をしました。
もし、要望やバグ等がありましたらGitHub
のIssue
かpull request
をお願いいたします。m_ _m
ではでは〜!