◆ ダイアログを出すなど認証時のやり取りは Basic 認証の仕組みで行う
◆ 成功時は Cookie を使って管理
  ◆ Basic 認証自体は成功させず 401 を返して Cookie をセット
  ◆ その後のログイン判断は Cookie で行う

以前 Chrome で 401 レスポンスを受け取ったときに response body を表示してくれないという記事を書きました
半年くらいたってますし そろそろ修正されたりしないかなと試してみると 表示できました

401 レスポンスでやりたかったことがあったのでやってみることにしました

Basic 認証で Cookie を使う

Basic 認証ってブラウザでダイアログを出してくれますし 楽ではありますが Cookie を使うものに比べて 認証後の扱いづらさがあると思います
Basic 認証の仕組みで id と password 送信するけど ログイン状態は Cookie で管理するというものがあれば便利そうです

ということで作ったものがこちらです

const http = require("http")

const realm = "xyz"
const basic_user = "abcd"
const basic_pass = "123"

http.createServer((req, res) => {
if (!req.url.startsWith("/auth")) {
res.end("ok")
return
}

if (req.url === "/auth/logout") {
res.setHeader("Set-Cookie", "user=; path=/")
res.end("logout")
return
}

const cookie = Object.fromEntries(
(req.headers.cookie || "").split(";").map((item) => item.split("="))
)

if (cookie.user) {
res.statusCode = 200
res.end(`You are ${cookie.user}.`)
return
}

const auth = req.headers.authorization
if (auth) {
const str = Buffer.from(auth.split(" ")[1], "base64").toString()
const [user, pass] = str.split(":")

if (basic_user === user && basic_pass === pass) {
res.statusCode = 401
res.setHeader("Set-Cookie", `user=${user}; path=/`)
res.end(`<script>location.reload()</script>`)
} else {
res.statusCode = 401
res.setHeader("WWW-Authenticate", `Basic realm="${realm}"`)
res.end("Unauthorized")
}
} else {
res.statusCode = 401
res.setHeader("WWW-Authenticate", `Basic realm="${realm}"`)
res.end("Unauthorized")
}
}).listen(8000)

仕組み

対象とするのは /auth の中だけにしています
それ以外は常に ok というレスポンスです

user という Cookie に名前が入っていればそのユーザでログイン中とみなし 入っていなければ未ログインとみなします
偽装し放題なので実際には Cookie に署名したり工夫がいりますがここでは省きます

/auth 内にアクセスがあった場合 Cookie を見てログイン中なら Cookie に含まれるユーザ名を表示します

未ログインの場合は 401 で Basic 認証を必要というレスポンスを返します
id と password が送られてきたらチェックします
ログインに成功した場合ですが Basic 認証を成功させたくはないので 401 を返します
初回と同じように WWW-Authenticate ヘッダーを送るとまたダイアログが出てしまい ログイン失敗時と同じになるので WWW-Authenticate ヘッダーは送りません
こうすると失敗になりダイアログが出ません
このときに一緒に Set-Cookie ヘッダーを送り Cookie にログイン情報をもたせます
ログイン成功時は Cookie にログイン情報が残り Basic 認証は失敗したという扱いです

これだと画面が Basic 認証に失敗した状態になるのでリロードし再度リクエストを送ります
今度は Cookie があるのでログイン済みのページが表示できます
リロードは script タグで location.reload() の JavaScript で実行してますが meta タグとかでもできます

ログアウトは /auth/logout にアクセスしたときで Cookie のログイン情報を削除します


このやり方では Basic 認証が成功してブラウザに Basic 認証の情報が残ることがないようにしましたが ここまで特殊なことしなくても Basic 認証はそのまま普通に使って 認証成功時に Cookie に追加情報を保存するでも十分な気はします