見出し画像

【身内しか見れないサイトを作る】webサイトにログイン機能を作る方法

なぜこの記事を書こうと思ったか

身内の掲示板を作ろうとしたときに親切な情報が少なく、苦労したから。
もう使われてません。何のために作ったんだ

公式サイトも意味不明で分かりずらかったから。

ちゃんとした実装方法を紹介しているサイトが少なかったから。
この方法がちゃんとしてるのかどうかは知りませんが。

目次

この記事のまとめ

Node.jsでユーザー関連の機能の実装によく使われる(?)Passport.jsの解説です。

ここでは、ユーザー名とパスワードを使用してログインする機能を実装する方法について解説します。ちょっと機能を足せば作れるので掲示板の機能は自分で頑張ってください。ソースコードにコメントもたくさん書いてあります。

ソースコードはGithubに上げてあります。publicディレクトリ以下にhtmlを置けばそれだけで会員限定になります。

環境

  • windows10

  • Node.js 20.17.0 LTS

  • npm 10.8.2

インストール方法は割愛します。

Expressについて

webアプリを作ったことがある人はご存知なはずです。アレです。
わかりやすい解説が出てると思うので、自分で調べてください。
解説は時間ができたら別記事に書きます。

まぁ割とコードを見ればわかるので知らない方も読み飛ばして問題ないです。

const express = require('express')
const app = express()

app.get("/test", (req, res) => {
    res.send("something")
})

また、app.route()を使うとこんな書き方ができます。今回はこの書き方はたぶん使いません。

const express = require('express')
const app = express()

app.route("/test")
    .get((req, res) => res.send("get '/test'"))
    .post((req, res) => res.send("post '/test'"))

さらに、appの代わりにexpress.Router()を使用すると、こんなこともできます。expressのモジュールを配布するときに便利です。これは割と大事なので覚えてください。今回も使う書き方になります。

// ------------------
// src/router_a.js
// ------------------

const express = require('express')
const router = express.Router()

router.get("/test", (req, res) => res.send("get router'/test'"))

module.exports = router
// ------------------
// src/app.js
// ------------------

const express = require('express')
const app = express()
const router_a = require('./router_a.js') 


// /router_a/testにgetしたときに先のファイルの
// router.get("/test", (req, res) => res.send("get router'/test'"))
// が発火するパターン
app.use("/router_a", router_a)


// /testにgetしたときに先のファイルの
// router.get("/test", (req, res) => res.send("get router'/test'"))
// が発火するパターン
app.use(router_a)

準備

レポジトリからクローンしてきてください。

npm install

package.jsonがあるディレクトリで実行してください。以上

もうこの時点でいろいろできます。

node index.js

でサーバーを起動し、localhost:3000にブラウザでアクセスしてください。ログインページが表示されるはずです。名前nasubiとパスワードsampleでログインできます。

ログインページなどはbootstrapを使って適当にスタイリングしました。

解説

見たほうが早いですが大事なところと私が躓いたところを書いていきます。
感覚的なわかりやすさ重視でだいぶ間違っていることを書いてる気がします。

ログインページ

ログインするときにはusernameとpasswordというパラメーターで認証情報を送信する必要があります。他の名前のパラメーターex. nameとpassではだめです。

フォームにして実装するのが早くて確実で簡単です。サーバーに送信するときはパスワードをハッシュ化したほうが良いかもしれませんが、その実装はしていません。平文で送信しています。

<form action="/login" method="POST">
    <div class="mb-3">
        <label for="username" class="form-label">Username:</label>
        <input type="text" id="username" name="username" class="form-control">    
    </div>
    <div class="mb-3">
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" class="form-control">    
    </div>
    <button type="submit" class="btn btn-primary">Login</button>
</form>

ログイン(サーバーサイド)

router.post('/login',
    passport.authenticate('local',
        {
            failureRedirect: '/login',//間違ったログイン情報のときのリダイレクト
            successRedirect: '/timeline'//正しいときのリダイレクト
        }
    )
);

ここで色々指定できます。パラメーターは他にもいろいろあった気がするので調べてみてください。これが呼ばれると下のlocalstrategyを使った認証が始まるという認識を私はしています。。感覚的な話。

passport.use(new LocalStrategy((username, password, done) => {
    let isuser = false;
    Users.forEach((user) => {
        //ユーザー名とパスワードで照会
        if (username == user.name && password == user.password) {
            isuser = { username: username, password: password };
        }
    })
    //もし正しいログイン情報ならdone(null, ユーザーの情報)
    //でなければdone(null, false)
    return done(null, isuser);
}));

入力されたユーザー名とパスワードが渡されるのでそれが正しいかをここで判定します。
doneの第二引数にユーザーの情報を入れてreturnします。
これがfalseなら認証は失敗判定になります。第一引数は何に使うんでしょうか。。。有識者教えてください。

認証が成功すると次は下のシリアライズメゾットが呼ばれるという認識を私はしています。感覚的な話。

passport.serializeUser((user, done) => {
    console.log(`Serialize with: ${JSON.stringify(user)}`);
    done(null, user);
});

たぶんここのuserにはlocalstrategyのdoneの第二引数が入ると思います。検証したことはありません。検証後更新します。

ここではおそらくセッションに保存する内容を選択していると思われます。
doneの第二引数がセッションに入るのかな?たぶん

ページにリクエストがあるたびに、このセッションの内容を読みだしてreq.userに入れています。この処理をデシリアライズメゾットに書きます。

passport.deserializeUser((user, done) => {
    console.log(`Deserialize with: ${JSON.stringify({ username: user.username })}`);
    done(null, { username: user.username });
});

これでapp.get()などされたときにdoneの第二引数がreq.userで取り出せるようになります。

静的ファイルにも制限を掛ける

req.isAuthenticated()でログイン状態が確認できます。

app.use('/public', (req, res, next) => {
    if (req.isAuthenticated()) {
        next();
    } else {
        res.redirect('/login');
    }
}, express.static('public'));

これは動的ページでも同じように動作します。next()の代わりにif文の中に直接処理を書いてもできます。静的ページはなぜかこの方法しかうまくいかなかった…

未認証リダイレクト

末尾

app.use("*", (req, res) => {
    if (req.isAuthenticated()) {
        res.status(404)
    } else {
        res.status(403).redirect("/login");
    }
})

と書くことでapp.get(), app.post()系はフィルターできるはず…

終わりに

ここまで読んでいただき、ありがとうございました!質問、間違い指摘などコメントお待ちしております!できるだけ返信・対応します!更新日時が数年前でもどしどし送ってきてください!

いいねもお願いします!サポートもしていただけるとものすごく喜びます!




この記事が気に入ったらサポートをしてみませんか?