【Express】認証パッケージを公開!

こんにちは!フリーランス・エンジニアの 九保すこひ です。

さてさて、もしかすると感の鋭い方はうっすらお気づきかもしれませんが、ここのところ公開している以下の記事はある「密かな野望」を狙ったものでした。

元々これらの記事は以下の記事を延長して作ったものですが、実はその「おわりに」で軽い気持ちで書いたことが「密かな野望」のきっかけになっています。

Expressにログイン機能をつくる

この記事で、2020年1月15日現在の私はこう書きました。

パッケージの initialize() で必要最低限はすべて実行してくれたらうれしいなー、なんてナマケモノ・プログラマーの私は思ってしまいました(笑)

特に重要な部分はここです。

すべて実行してくれたらうれしいなー

つまり、Laravelのように以下5つの機能を一気に使えるようになったら楽でいいよねー、といういかにもナマケモノらしい発想です。

  • ログイン
  • ユーザー登録
  • パスワード再発行
  • 自動ログイン(次回から省略)
  • CSRF対策

正直なところ、パッケージをつくるのは労力がいるのでどうしようか少し考えていたのですが、あれば便利ですし、何より作ってみるのが面白そうだったので、昨年一斉を風靡した言葉をパロディしながら開発をはじめてみました。

『ナマケモノ、動きます。』

ということで、今回はExpressに総合的なログイン機能を追加する「Express-Ticket」公開記念として、使い方をご紹介したいと思います。

名前の由来

元祖の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に強制的にリダイレクトされます。

また、例えばユーザー情報のroleadminのものだけアクセスできるようにする場合はこうなります。

※ただし、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本体のデータにアクセスできないからです。

そこで、これを解決した方法がreqresへの独自メソッドの追加でした。

つまり、以下のように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からデータを取得しようとすると、必ずPromiseasync + awaitで取得をしないといけません。

当初は可読性が高いことからasync + awaitを使っていたのですが、ある場所でこれを使ってしまうと、その呼び出し元も、さらにその呼び出し元も…となり、延々とasync + awaitを強制される結果になったので、最終的にできるだけPromiseを使って処理をするようにしました。(このへんはNode.jsの弱点だと思っています)

CSRF対策の開発

冒頭でも書きましたが、このパッケージはCSRF対策が使えます。

※ CSRF攻撃については以下をご覧ください。

CSRF(クロスサイト・リクエスト・フォージェリ)攻撃をできるだけ分かりやすく解説

CSRF対策をするには、一旦作ったランダムな文字列をセッションにいれておいて、データの送信後にその文字列をチェックすることで実装しますが、Node.jsはコードを変更して再読み込みすると、なんとセッションがクリアされてしまうので、コードを変更する度にページをリロードし、再び入力して確認する、という作業を何度もしないといけなかったのがなかなか大変でした😅

解決法はこれといってありません。ゴメンナサイ。m_ _m

コードを探す作業

Express-Ticketは、もともと私が大好きなLaravelにインスパイアされてつくったパッケージなので、Laravelの実装はどうなっているかをひとつひとつチェックする必要がありました。

その中で、いくつもコードを追っていったのですが(phpstormctrl + クリックで定義場所まで飛んでくれるのでまだ楽でしたが👍)、ある部分はInterfaceで実装していて実際のコードがどこにあるかが分からないということが何度もあり、探す作業に時間がとられるという結果になりました。

ただ、この作業をやったおかげで、よりLaravelの中身を知ることができスキルアップに繋がったと思います。

やっぱりLaravelはすごいです😊✨

おわりに

ということで、今回はLaravelにインスパイアされて開発したログイン総合パッケージExpress-Ticketのご紹介をしました。

もし、要望やバグ等がありましたらGitHubIssuepull requestをお願いいたします。m_ _m

ではでは〜!

この記事が役立ちましたらシェアお願いします😊✨ by 九保すこひ
このエントリーをはてなブックマークに追加       follow us in feedly