JSON Webトークンとの使用方法
JSON Webトークン (JWT) は、JSONオブジェクトとして当事者間で情報を安全に送信するためのコンパクトで自己完結型の方法を定義するオープンスタンダード (RFC 7519) です。この情報はデジタル署名されているため、検証して信頼することができます。
オーバーヘッドが小さく、異なるドメイン間で簡単に使用できるため、認証によく使用されます。
詳細はこちら https://jwt.dokyumento.jp。
基本的なアプリケーションから始めましょう
- CommonJS
- ES モジュール
- TypeScript
const express = require("express");
const { createServer } = require("node:http");
const { join } = require("node:path");
const passport = require("passport");
const passportJwt = require("passport-jwt");
const JwtStrategy = passportJwt.Strategy;
const ExtractJwt = passportJwt.ExtractJwt;
const bodyParser = require("body-parser");
const { Server } = require("socket.io");
const jwt = require("jsonwebtoken");
const port = process.env.PORT || 3000;
const jwtSecret = "Mys3cr3t";
const app = express();
const httpServer = createServer(app);
app.use(bodyParser.json());
app.get("/", (req, res) => {
  res.sendFile(join(__dirname, "index.html"));
});
app.get(
  "/self",
  passport.authenticate("jwt", { session: false }),
  (req, res) => {
    if (req.user) {
      res.send(req.user);
    } else {
      res.status(401).end();
    }
  },
);
app.post("/login", (req, res) => {
  if (req.body.username === "john" && req.body.password === "changeit") {
    console.log("authentication OK");
    const user = {
      id: 1,
      username: "john",
    };
    const token = jwt.sign(
      {
        data: user,
      },
      jwtSecret,
      {
        issuer: "accounts.examplesoft.com",
        audience: "yoursite.net",
        expiresIn: "1h",
      },
    );
    res.json({ token });
  } else {
    console.log("wrong credentials");
    res.status(401).end();
  }
});
const jwtDecodeOptions = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: jwtSecret,
  issuer: "accounts.examplesoft.com",
  audience: "yoursite.net",
};
passport.use(
  new JwtStrategy(jwtDecodeOptions, (payload, done) => {
    return done(null, payload.data);
  }),
);
const io = new Server(httpServer);
httpServer.listen(port, () => {
  console.log(`application is running at: https://:${port}`);
});
import express from "express";
import { createServer } from "node:http";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import passport from "passport";
import { Strategy as JwtStrategy, ExtractJwt } from "passport-jwt";
import bodyParser from "body-parser";
import { Server } from "socket.io";
import jwt from "jsonwebtoken";
const port = process.env.PORT || 3000;
const jwtSecret = "Mys3cr3t";
const app = express();
const httpServer = createServer(app);
app.use(bodyParser.json());
const __dirname = dirname(fileURLToPath(import.meta.url));
app.get("/", (req, res) => {
  res.sendFile(join(__dirname, "index.html"));
});
app.get(
  "/self",
  passport.authenticate("jwt", { session: false }),
  (req, res) => {
    if (req.user) {
      res.send(req.user);
    } else {
      res.status(401).end();
    }
  },
);
app.post("/login", (req, res) => {
  if (req.body.username === "john" && req.body.password === "changeit") {
    console.log("authentication OK");
    const user = {
      id: 1,
      username: "john",
    };
    const token = jwt.sign(
      {
        data: user,
      },
      jwtSecret,
      {
        issuer: "accounts.examplesoft.com",
        audience: "yoursite.net",
        expiresIn: "1h",
      },
    );
    res.json({ token });
  } else {
    console.log("wrong credentials");
    res.status(401).end();
  }
});
const jwtDecodeOptions = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: jwtSecret,
  issuer: "accounts.examplesoft.com",
  audience: "yoursite.net",
};
passport.use(
  new JwtStrategy(jwtDecodeOptions, (payload, done) => {
    return done(null, payload.data);
  }),
);
const io = new Server(httpServer);
httpServer.listen(port, () => {
  console.log(`application is running at: https://:${port}`);
});
import express from "express";
import { type Request, type Response } from "express";
import { createServer } from "node:http";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import passport from "passport";
import { Strategy as JwtStrategy, ExtractJwt } from "passport-jwt";
import bodyParser from "body-parser";
import { Server } from "socket.io";
import jwt from "jsonwebtoken";
declare global {
  namespace Express {
    interface User {
      id: number;
      username: string;
    }
  }
}
const port = process.env.PORT || 3000;
const jwtSecret = "Mys3cr3t";
const app = express();
const httpServer = createServer(app);
app.use(bodyParser.json());
const __dirname = dirname(fileURLToPath(import.meta.url));
app.get("/", (req, res) => {
  res.sendFile(join(__dirname, "index.html"));
});
app.get(
  "/self",
  passport.authenticate("jwt", { session: false }),
  (req, res) => {
    if (req.user) {
      res.send(req.user);
    } else {
      res.status(401).end();
    }
  },
);
app.post("/login", (req, res) => {
  if (req.body.username === "john" && req.body.password === "changeit") {
    console.log("authentication OK");
    const user = {
      id: 1,
      username: "john",
    };
    const token = jwt.sign(
      {
        data: user,
      },
      jwtSecret,
      {
        issuer: "accounts.examplesoft.com",
        audience: "yoursite.net",
        expiresIn: "1h",
      },
    );
    res.json({ token });
  } else {
    console.log("wrong credentials");
    res.status(401).end();
  }
});
const jwtDecodeOptions = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: jwtSecret,
  issuer: "accounts.examplesoft.com",
  audience: "yoursite.net",
};
passport.use(
  new JwtStrategy(jwtDecodeOptions, (payload, done) => {
    return done(null, payload.data);
  }),
);
const io = new Server(httpServer);
httpServer.listen(port, () => {
  console.log(`application is running at: https://:${port}`);
});
これらの追加の型が必要です
npm install @types/express @types/jsonwebtoken @types/passport @types/passport-jwt
この例では、`/login` ハンドラーで手動でトークンを作成していますが、独自のアプリケーションのどこか別の場所から取得される場合があります。
クライアント側では、トークンは `Authorization` ヘッダーに含まれています
const socket = io({
  extraHeaders: {
    authorization: `bearer ${myToken}`
  }
});
これは、HTTPロングポーリングが有効になっていて、最初に使用されている場合にのみ機能します。ブラウザはWebSocket接続に追加のヘッダーを提供する方法を提供していないためです。
// THIS WON'T WORK
const socket = io({
  transports: ["websocket"],
  extraHeaders: {
    authorization: `bearer ${myToken}`
  }
});
ユーザーコンテキストの共有
ユーザーコンテキストは、以下を呼び出すことによって Socket.IO サーバーと共有できます
io.engine.use((req, res, next) => {
  const isHandshake = req._query.sid === undefined;
  if (isHandshake) {
    passport.authenticate("jwt", { session: false })(req, res, next);
  } else {
    next();
  }
});
`isHandshake` チェックにより、ミドルウェアがセッションの最初の HTTP リクエストにのみ適用されることが保証されます。
これで `user` オブジェクトにアクセスできるようになります
io.on("connection", (socket) => {
  const user = socket.request.user;
});
手動解析
上記の例では、`passport-jwt` パッケージを使用していますが、`jsonwebtoken` パッケージを使用してベアラートークンを手動で検証することもできます。
io.engine.use((req, res, next) => {
  const isHandshake = req._query.sid === undefined;
  if (!isHandshake) {
    return next();
  }
  const header = req.headers["authorization"];
  if (!header) {
    return next(new Error("no token"));
  }
  if (!header.startsWith("bearer ")) {
    return next(new Error("invalid token"));
  }
  const token = header.substring(7);
  jwt.verify(token, jwtSecret, (err, decoded) => {
    if (err) {
      return next(new Error("invalid token"));
    }
    req.user = decoded.data;
    next();
  });
});
ユーザーIDの使用
ユーザーIDを使用して、Express と Socket.IO の間のリンクを作成できます
io.on("connection", (socket) => {
  const userId = socket.request.user.id;
  // the user ID is used as a room
  socket.join(`user:${userId}`);
});
これにより、特定のユーザーのすべての接続にイベントを簡単にブロードキャストできます
io.to(`user:${userId}`).emit("foo", "bar");
ユーザーが現在接続されているかどうかを確認することもできます
const sockets = await io.in(`user:${userId}`).fetchSockets();
const isUserConnected = sockets.length > 0;
JSON Web トークンとの互換性については以上です。読んでくれてありがとう!
完全な例はこちらにあります https://github.com/socketio/socket.io/tree/main/examples/passport-jwt-example。
- CommonJS
- ES モジュール
- TypeScript
この例は、ブラウザで直接実行できます。
この例は、ブラウザで直接実行できます。
この例は、ブラウザで直接実行できます。