From 12dff1deced5ce32d9cb0911c47e7b05ae52284d Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Fri, 1 Nov 2024 08:06:26 +0200 Subject: [PATCH] feature: add client side cookie to expose login info Webapps are often used in multiple windows and the browser's login status in session cookie may change in another window or in another webapp (Admin UI vs Freeboard, Kip etc). The regular session cookie is set to be httpOnly, so it is not available for clientside code. This adds a separate cookie that is set and removed by the server when the user's login status changes. A webapp can poll for changes of this cookie to react. One notable use case for this is applicationData that is available only when the user/browser is logged in. --- docs/src/develop/webapps.md | 2 ++ src/tokensecurity.js | 14 +++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/src/develop/webapps.md b/docs/src/develop/webapps.md index f71d998ed..e99d403ad 100644 --- a/docs/src/develop/webapps.md +++ b/docs/src/develop/webapps.md @@ -197,6 +197,8 @@ The login endpoint has an optional `rememberMe` request parameter. By default, w As the cookie is set to be [`HttpOnly`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#security) webapp JavaScript has no access to it. Including it in server requests and persisting its value is managed by the browser, governed by the `Set-Cookie` headers sent by the server. +Additionally the server sets cookie `skLoginInfo` when the user logs in and removes it when the user logs out. A webapp can poll for changes of this cookie to be notified of the browser's cookie based login status. + For **token based sessions** a webapp may manage the authentication token itself. It must include it explicitly in fetch call headers. As JavaScript has no access to headers but cookies are included automatically by browsers when opening WebSocket connections the server will use the server-set, HttpOnly cookie. Normally browsers do not allow shadowing the server-set cookie with a new value. The only option for WebSocket connections is using a query parameter to override the cookie with a token. diff --git a/src/tokensecurity.js b/src/tokensecurity.js index 5220d941d..4260ac0e8 100644 --- a/src/tokensecurity.js +++ b/src/tokensecurity.js @@ -36,6 +36,10 @@ const permissionDeniedMessage = const skPrefix = '/signalk/v1' const skAuthPrefix = `${skPrefix}/auth` + +//cookie to hold login info for webapps to use +const BROWSER_LOGININFO_COOKIE_NAME = 'skLoginInfo' + import { SERVERROUTESPREFIX } from './constants' const LOGIN_FAILED_MESSAGE = 'Invalid username/password' @@ -198,6 +202,13 @@ module.exports = function (app, config) { ) } res.cookie('JAUTHENTICATION', reply.token, cookieOptions) + // eslint-disable-next-line no-unused-vars + const { httpOnly, cookieOptionsForBrowserCookie } = cookieOptions + res.cookie( + BROWSER_LOGININFO_COOKIE_NAME, + JSON.stringify({ status: 'loggedIn', user: reply.user }) + ), + cookieOptionsForBrowserCookie if (requestType === 'application/json') { res.json({ token: reply.token }) @@ -237,6 +248,7 @@ module.exports = function (app, config) { app.put(['/logout', `${skAuthPrefix}/logout`], function (req, res) { res.clearCookie('JAUTHENTICATION') + res.clearCookie(BROWSER_LOGININFO_COOKIE_NAME) res.json('Logout OK') }) ;[ @@ -295,7 +307,7 @@ module.exports = function (app, config) { debug(`jwt expiration:${JSON.stringify(jwtOptions)}`) try { const token = jwt.sign(payload, configuration.secretKey, jwtOptions) - resolve({ statusCode: 200, token }) + resolve({ statusCode: 200, token, user: user.username }) } catch (err) { resolve({ statusCode: 500,