Hi all and once again welcome to a New Year.
At the moment, I am trying to work out the best approach to securing uibuilder web apps while trying to keep a decent measure of simplicity.
I'd like to share my current thoughts with anyone who may be interested - hence this post.
The master for this post is in the WIKI and I will update that as I go along. Please use this thread however, for discussion and ideas.
A proposal for uibuilder security (v2)
This is a write-up of my latest proposal for securing user interfaces built with uibuilder and Node-RED. It is still subject to change and I'm happy to take feedback.
There is a separate code branch for the security build - boringly called the Security branch. You are welcome to try it out but please note that it may not always work as expected and will contain all sorts of weird debugging. I will update the forum when the code reaches beta quality.
Requirements
- Should be as simple as possible to use - uibuilder itself should hide much of the complexity.
- Must be optional (not everyone requires UI security).
- Must be independent of Node-RED core security.
- Must allow for custom user validation. User data must be outside of uibuilder and outside of Node-RED for security.
- Must allow for custom session creation/validation/destruction.
- Must allow for timed session validity.
- Must warn if not using TLS. (If in production, should error out if not using TLS).
- Must provide security for websocket communications.
Assumptions
- YOU are responsible for the security of your data, users and systems. Not me. Sorry. No guarantees or warranties are given or implied. Get your systems audited, tested and certified by professionals.
- There is no point using logins if you don't secure communications using TLS. If you try (for testing maybe?), you will get continual warnings in your Node-RED logs.
- Only you know how to validate whether someone is who they claim to be. I can't know that. So you have to write the code for that part. I will provide a simplistic and basic method as a starting point.
- Your UI should not include any sensitive data and therefore should not need a login before delivery of the core resources to a client.
- uibuilder will use websocket-based login, not HTTP(s) or an API. (I may create an API method at some point if enough people feel strongly enough about this).
- Websocket broadcasts and uibuilder control messages will be permitted (from server to clients) without authentication. uibuilder control messages will be permitted from the clients to the server without authentication.
- I will use JavaScript Web Tokens (JWT) to make life easier. However, JWT is NOT a security mechanism (amazing how many people get that wrong), it is a convenience mechanism. JWT is liable to session hijack and man-in-the-middle attacks. So token lifespan should be short and ongoing session validation is important.
- Tokens should be cryptographically secure. To make this easy, uibuilder will take care of it for you.
- uibuilder relies heavily on websocket communication. Unfortunately, ws does not support custom headers except on initial handshake so standard methods for session validation don't work. Therefore, uibuilder will provide a custom method.
- User & session validation will use a custom Node.js module (written by you though I will provide something basic as a starter), changes to the module will require reloading Node-RED. See bullet 2 above.
- Session management requires a session per client. A single user might have many clients open (multiple desktop browser tabs, mobile browser tabs, etc). So this can mount up. Be aware of resource utilisation.
- Session management requires a check for each client for each message sent/received. That's a LOT of checks, session checks need to be efficient and short.
- Session management will be memory based by default. Restarting Node-RED will invalidate all sessions. This may change.
Proposal
Authentication checks will happen via a call to uibuilder.logon(userData)
from your front-end code. You may pass an object containing additional user information as the single parameter. At minimum, you need to pass some kind of identifier. Typically, you will also give a password.
That data will be collected from your user via a form typically, an internal variable tells you whether the current user is authenticated (uibuilder.get('isAuthorised')
). If they are authorised, another internal variable holds the token and a 3rd holds the token expiry date/time. The token will be passed back to the server every time the client sends a message so that it can be validated.
If the server receives an invalid token, it will invalidate the session (and may log the user out of all their sessions).
When the server receives a logon
control msg from a client, it will call a logon function which will reference your external module to validate the user information. Assuming the user data is valid, the external function either returns true
or an object with a defined schema (which allows you to return information back to the user such as messages to pay dues, reminders to change passwords, or anything else that you like).
Upon validation of the user, uibuilder will create a session entry for that connection/user. Sessions may (and should) have a timeout. Session creation, destruction and validation are also done by your external module. Session creation and optionally validation functions may return an object which will be folded into the JWT which uibuilder will create.
As yet undecided
I've not quite worked out the best approaches for these as yet.
- It is yet possible that I'll hand over session management to a standard library. However, I suspect that none are flexible enough & not many properly handle websocket sessions.
- There may be a need for 2 levels of session validation? One that will refresh the token on a short time period (typically 30 seconds as this is how often your client will send a
ping
message to the server). The second will typically be on a longer time period that you will choose, typically a few days. - Invalid tokens received by the server might cause a user to be logged out of all their sessions?
Settings
- Security (Boolean} will be optional via a setting for each uibuilder node instance.
- Session quick refresh {Boolean} will enable a refresh/quick validation function to be called for every 'packet' (e.g. any kind of message including
pings
which happen every 30 seconds). This helps reduce the risk of using JWT and ensures that sessions continue to get extended even if the user isn't interacting. - Session expiry {Number} will be controllable for each instance of the uibuilder node. This is the maximum session length. After this period, the session quick refresh will stop and the user must log in again (unless you do something clever on the front-end to resend details automatically - that has some risk of course in retaining sensitive data in the browser).
- User & session validation {Module} will use a custom Node.js module, changes to the module will require reloading Node-RED. This will support the use of a central module that will be found in
<uibRoot>/.config/security.js
or alternatively, in the instances root<uibRoot>/<url>/security.js
. This allows for different instances of uibuilder to have different authentication and session validation functions. A template example file will be provided in<uibRoot>/.config/security.js
that can be adjusted or replaced as needed.
Other Info
Also see the original security design page.