"Remember Me" for Node-RED Dashboard?

I have enabled user authentication for my NR Dashboard, which is a good thing. However, when I open the dashboard on my phone, for example, I have to enter my user ID and password each and every time. Is there a way to get the NR Dashboard to remember me and not require me to enter my credentials each time?

I'm running Node-RED on a Raspberry Pi 4 (8GB RAM/64GB Micro-SD) along with Mosquitto. I can access the Node-RED Dashboard on my LAN as well as publicly over TLS with user auth.

Thanks!
--- Jeff

Hi Jeff,
IT seems like you are looking for something like a push notification. That is not yet supported by NR. Afaik. Because you can't "skip" the authentication.

Some possibilities:
-use a mqtt service which is publicly accessible, and use a smartphone app that notifies you at incoming msg. That is not very reliable as you would have to reconnect to the mqtt everytime you lose the internet connection if not done by the app itself. But works theoretically.

Better:
-If you are an Alexa-user you can use the Alexa contrib remote node to send notifications to your alexa app. That works very well and you won't have to set up you own stuff at an aws service by yourself

Even better and my particular choice:
I have one NR instance running as plugin In HassIO on a raspberry pi. HomeAssistant has a companion app for android and iOs that can gather you mobile device sensor data and send it to your NR instance. You can do a LOT in blooming heck of cool stuff with this! And it receives push notifications from your NR as you wish, that you can fully personalize yourself. Including the notification title (the bold) text.

Edit:
And to be complete about this. The dashboard notifications are nice. But not if you are not logged in of course. That's why I use my mobile wifi (you can use ping as well or whatever you like that succeeds, geofence,...) Whether I'd like to have a dashboard notification or a mobile notification.

Hi Lindsay,
I might be mistaken, but I think that Jeff is talking about the logon popup of the dashboard. I have the same on my Android smartphone. Most of the times I have to enter my username and password, which is very annoying. And sometimes he remembers it. I assume that might obvious for some folks, but I also don't know why that happens...

That is not entirely true: see node-red-contrib-ui-web-push. Although it doesn't work on iOs, and I haven't published it on NPM yet...

@BartButenaers You are correct. I am looking for a solution where the user name and password can somehow be "remembered" on my Android phone so that I don't have to sign in every time I open it.

I am relatively new to the Node-RED/Dashboard ecosystem so I am not (yet) fully familiar with all the available features and what can be customized and what can't. For example, is there any way to enable some sort of user auth session/state that can be persisted (e.g. something akin to an auth token)?

Thanks!
--- Jeff

Oh sorry, I understood sth like: "remember me" on something...

Yes that depends on the browser you are using (?).most of them offer the "remember me" question, when you put in your credentials. I use chrome on android and windows and chromium on Linux. I entered them exactly once. Of course in this case I have to trust google, for the sake of comfort...

1 Like

Hi Jeff,

The authentication does create a token which persists between sessions. The default is 7 days which can be changed within settings.js (Securing Node-RED : Node-RED).

adminAuth: {
    sessionExpiryTime: 86400,
    ...
}

This can be verified within on a desktop computer by inspecting the website and looking at Local Storage, You will see an access_token as well as an expires_in value (in seconds) for how long the token will remain valid. You will also see a .sessions.json file in root folder which will show this same access token with the expiration date.

  • If you log-on from a different device with the same username, it will overwrite this token and you'll not be automatically signed in on the previous device. If you're consistently switching between multiple devices consider creating additional accounts for each device.
  • This does not work in private mode as this is data that will be erased upon leaving the site.

I do not have an android phone to test this out, but it works as explained above on my iPhone (safari automatically deletes local storage items over 7 days old, so extending the session expiration doesn't help there).

If the additional account doesn't fix this for you try searching for your specific browser and how Local Storage is being managed when closing the app (it might be configurable within your settings).

@ChrisZ Yes, this works just fine for the "Admin" screen (i.e. where you setup your node flows). However, this does not appear to apply to the Dashboard URL, which is what I'm really looking for. The security page you referenced shows how to lock down the dashboard via httpNodeAuth but does not appear to have a similar setting for sessionExpirtyTime.

It's starting to look like this isn't support out of the box. I'd be curious if anyone has any ideas as to how hard this would be to implement - or if it's even worth it? It's really just a matter of convenience so that every time I pop the dashboard open on my phone, I don't have to re-enter the credentials (though, I suppose I could let the browser save the credentials as make it a bit easier).

Unless anyone comes up with another/better idea, I will assume this is an unsupported feature. I'll poke around to see if I can figure out how to add a request (maybe it's already there?).

Thanks!

For the dashboard looks like it's a http-authentication protocol. Unfortunately this does not persist through closing the browser. The dashboard does allow custom middleware to handle the authentication instead of the default one. If you remove httpNodeAuth & httpStaticAuth and handle the users within the middleware, it should get to what you want:

    ui: { 
        path: "ui",
        middleware: require('./dashboardMiddleware')
    },

dashboardMiddleware.js @ root (or you can put this all in the settings file)

module.exports = (req,res,next) => {
    // To get a hashed password run the following command:
    //   node -e "console.log(require('bcryptjs').hashSync(process.argv[1], 8));" your-password-here
    // sessionExpiryTime is duration in seconds for session to remain valid
    const settings = {
        userAuth: {
            type: "credentials",
            users: [
                {
                    username: "admin",
                    password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.",
                    permissions: "*"
                },{
                    username: "user",
                    password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.",
                    permissions: "*"
                }
            ]
        },
        sessionFile: './.sessions.json',
        sessionExpiryTime: 604800
    }
    const sessionInfo = {
        headers_accept_language: req.headers['accept-language'],
        headers_host: req.headers.host,
        headers_user_agent: req.headers['user-agent'],
        headers_client_address: req.header('x-forwarded-for') || req.connection.remoteAddress
    };

    const bcrypt = require('bcryptjs');    
    const createSession = (scope) => {
        const sessionHash = bcrypt.hashSync(JSON.stringify(sessionInfo), 8);
        const expires = Date.now() + (settings.sessionExpiryTime*1000);
        const client = 'node-red-dashboard';
        return {sessionHash, expires, client, scope}
    }
    
    const fs = require('fs');
    let session = {};
    try {
        if (fs.existsSync(settings.sessionFile)) {
            session = require(settings.sessionFile);
        }
    } catch (err) {
        console.log({err});
    }
    const tokens = [];
    Object.keys(session).forEach(sessionKey => tokens.push(sessionKey));
    for (let i = 0; i < tokens.length; i++) {
        if (bcrypt.compareSync(JSON.stringify(sessionInfo), tokens[i])) {
            if (session[tokens[i]].expires < Date.now()) {
                delete session[tokens[i]];
                fs.writeFileSync(settings.sessionFile, JSON.stringify(session));
                res.set('WWW-Authenticate', 'Basic realm="Authorization Required"');
                return res.sendStatus(401);
            } else {
                return next();
            }
        }
    }

    const authHeader = req.headers.authorization;
    if (authHeader) {
        const token = authHeader.split(' ')[1];
        const [username, pass] = new Buffer.from(token, 'base64').toString('ascii').split(':');        
        for (let i = 0; i < settings.userAuth.users.length; i++) {
            if (username === settings.userAuth.users[i].username) {
                if(bcrypt.compareSync(pass, settings.userAuth.users[i].password)) {
                    const sessionJSON = createSession(settings.userAuth.users[i].permissions);
                    session[sessionJSON.sessionHash] = sessionJSON;
                    fs.writeFileSync(settings.sessionFile, JSON.stringify(session));
                    return next();
                }
            }
        };
        res.set('WWW-Authenticate', 'Basic realm="Authorization Required"');
        return res.sendStatus(401);
    }
    res.set('WWW-Authenticate', 'Basic realm="Authorization Required"');
    return res.sendStatus(401);
};

This creates a fingerprint of your device and allows repeated logins for a duration of time that you set. IP address changes and browser changes/updates will require logging in again. This does not overwrite any existing sessions so the same account login works from multiple devices.

1 Like

@ChrisZ - Thanks! I'll give this a try over the next day or so and report back how it worked for me.