Last active
January 25, 2025 04:15
-
-
Save takayamaekawa/bcaeeb99584272870308996b66a7e756 to your computer and use it in GitHub Desktop.
セキュアなウェブソケット通信を目指してる
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // views/chat.ejs | |
| // https://github.com/bella2391/FMCWebApp/blob/master/src/views/chat.ejs | |
| // ローカル環境でもリモート環境でも動くように動的なwsのURLをfetchAPIで取得していますが、気にしないでください。 | |
| <html> | |
| ... | |
| <input type="hidden" id="_csrf" value="<%= csrfToken %>"> | |
| <input type="hidden" id="token" value="<%= token %>"> | |
| <script> | |
| const csrfTokenInput = document.getElementById('_csrf'); | |
| const tokenInput = document.getElementById('token'); | |
| if (!csrfTokenInput || !tokenInput) { | |
| console.error('Invaild Access'); | |
| return; | |
| } | |
| const csrfToken = csrfTokenInput.value; | |
| const token = tokenInput.value; | |
| fetch('/api/config') | |
| .then(response => response.json()) | |
| .then(config => { | |
| const ws = new WebSocket(`${config.websocketUrl}?token=${token}`); | |
| ws.addEventListener('open', () => { | |
| ws.send(JSON.stringify({ | |
| user: htmlspecialchars(name), | |
| method: 'connect', | |
| csrfToken | |
| })); | |
| }); | |
| ... | |
| }); | |
| </script> | |
| ... | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // routes/chat.ts | |
| // https://github.com/bella2391/FMCWebApp/blob/master/src/routes/chat.ts | |
| // `jwt.sign(...)`で渡す`payload`の中に、`csrfToken`をいれる | |
| // (`csrfToken`をJWTの`sign`配列でラップする) | |
| router.get('/', (req: Request, res: Response) => { | |
| const csrfToken = req.csrfToken ? req.csrfToken() : undefined; | |
| if (!csrfToken) { | |
| res.status(400).send('Invalid Access'); | |
| return; | |
| } | |
| const payload: Jsonwebtoken.WebSocketJwtPayload = { csrfToken }; | |
| const token: string = jwt.sign(payload, JWT_SECRET, { expiresIn: '1h' }); | |
| const encodedToken = encodeURIComponent(token); | |
| res.render(`chat`, { token: encodedToken }); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // @types/[].d.ts | |
| // https://github.com/bella2391/FMCWebApp/blob/master/src/%40types/express/index.d.ts | |
| declare global { | |
| namespace Jsonwebtoken { | |
| interface UserAuthJwtPayload extends JwtPayload { | |
| id: string; | |
| name: string; | |
| email: string; | |
| } | |
| interface WebSocketJwtPayload extends JwtPayload { | |
| csrfToken: string; | |
| } | |
| } | |
| namespace http { | |
| interface IncomingMessageWithPayload extends IncomingMessage { | |
| payload?: Jsonwebtoken.WebSocketJwtPayload; | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // middlewares/localvals.ts | |
| // https://github.com/bella2391/FMCWebApp/blob/master/src/middlewares/localvals.ts | |
| // 後の__chat.ejs__テンプレートに渡します(常にcsrf用のトークンが生成&__res.locals__へ入る) | |
| import csurf from 'csurf'; | |
| ... | |
| const localvals = (req: Request, res: Response, next: NextFunction) => { | |
| res.locals.csrfToken = req.csrfToken ? req.csrfToken() : ''; | |
| ... | |
| }; | |
| export default localvals; | |
| ... |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // services/websocket.ts | |
| // https://github.com/bella2391/FMCWebApp/blob/master/src/services/websocket.ts | |
| import { WebSocket, WebSocketServer } from 'ws'; | |
| const isAuthenticated = (json, payload: Jsonwebtoken.WebSocketJwtPayload): boolean => { | |
| const jsonCsrfToken = json.csrfToken; | |
| const payloadCsrfToken = payload.csrfToken; | |
| return jsonCsrfToken && payloadCsrfToken && jsonCsrfToken === payloadCsrfToken; | |
| }; | |
| const websocket = () => { | |
| const wss = new WebSocketServer({ noServer: true }); | |
| const clients = new Set<WebSocket>(); | |
| wss.on('connection', (ws: WebSocket, request: http.IncomingMessageWithPayload) => { | |
| console.log('client has connected'); | |
| const { payload } = request; | |
| if (!payload) { | |
| ws.close(); | |
| throw new Error('Invalid Access') | |
| } | |
| clients.add(ws); | |
| ws.on('message', (msg: string) => { | |
| const json = JSON.parse(msg); | |
| if (!isAuthenticated(json, payload)) { | |
| ws.close(); | |
| throw new Error("Invalid Access"); | |
| } | |
| // 通常のメッセージ処理 | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // bin/www.ts | |
| // https://github.com/bella2391/FMCWebApp/blob/master/src/bin/www.ts | |
| ... | |
| server.on('upgrade', (request, socket, head) => { | |
| if (!request.url) { | |
| throw new Error("Invalid Access"); | |
| } | |
| const reqUrl = getHPURL(false) + request.url; | |
| try { | |
| const parsedUrl = new URL(reqUrl); | |
| if (parsedUrl.pathname === basepath.wsrootpath) { | |
| const query = parsedUrl.searchParams; | |
| const token = query.get('token'); | |
| if (!token) { | |
| throw new Error("Invalid Access"); | |
| } | |
| const decodeToken = decodeURIComponent(token); | |
| const payload = jwt.verify(decodeToken, JWT_SECRET) as Jsonwebtoken.WebSocketJwtPayload; | |
| (request as any).payload = payload; | |
| wss.handleUpgrade(request, socket, head, (ws) => { | |
| wss.emit('connection', ws, request); | |
| }); | |
| } | |
| } catch (error) { | |
| console.error('socket error:', error); | |
| socket.destroy(); | |
| } | |
| }); | |
| ... |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment