Create server API with Token

I was thinking of recommending a proxy for the authentication. Surely it would be more secure. The thing is that most of the more advanced features of Nginx seem to be included in nginx Plus which needs a yearly license ? Thats why i started reading about Kong Gateway which is based on nginx but with more free plugins/features. and we love our free and open-source software lol

@LinkView_Maze that looks like an interesting solution ..
that node-red-contrib-httpauth node is 7 years old! ... well ... if it works it works
I didnt install the missing nodes but i tried to understand the flow
As it is, you are saving a token in a file and then with the api request you see if it matches ==
How does that work with multiple users my friend ? :wink:
maybe im wrong but in some cases you will be trying to match the token of user2 with the saved token of user1 ?


I was playing around a bit with the JWT example

added an sqlite db with a Users table for the credential check

image

added the flow part for the API and the verification of the jwt token

Used the Postman software for testing

Reguest to https://<node-red-ip>:1880/login
with a JSON body { "username": "user1", "password": "abc123" }
returns the token for user1 (with test expiration 30 seconds)

{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJ1c2VyMSIsImlhdCI6MTY1NzE4ODI4NCwiZXhwIjoxNjU3MTg4MzQ0fQ.VI4uxgAUoQ9NoPv0YIKpXJQxiEw82zxj937CyjX"}

which can be used for the API requests with Bearer Token Authorization
https://<node-red-ip>:1880/api

Result :

image

After time-limit

image

Example Flow

[{"id":"7fdf168306547292","type":"function","z":"54efb553244c241f","name":"sign JWT","func":"// we have a user match from the db \nif (msg.payload.length == 1) {\n\n// the result from the db (should be a single element)\n    let user = {\n        id: msg.payload[0].id,\n        username: msg.payload[0].username\n    }   \n\n    jwt.sign( user , 'secretkey', { expiresIn: '30s' }, (err, token) => {\n        msg.payload = { token };\n    });\n\n}\n\nelse {\n    msg.payload = \"Unauthorized\"\n    msg.statusCode = \"403\"\n\n}\n\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"jwt","module":"jsonwebtoken"}],"x":800,"y":1620,"wires":[["7ac97ab8768e5b00"]]},{"id":"9b8747bdc731f290","type":"http in","z":"54efb553244c241f","name":"","url":"/login","method":"post","upload":false,"swaggerDoc":"","x":190,"y":1620,"wires":[["6c2312a5ea74a554","1706dfda582cf6db"]]},{"id":"6c2312a5ea74a554","type":"debug","z":"54efb553244c241f","name":"debug 1","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":340,"y":1560,"wires":[]},{"id":"1706dfda582cf6db","type":"function","z":"54efb553244c241f","name":"prepare sql","func":"let username = msg.payload.username;\nlet password = msg.payload.password;\n\nmsg.topic = `SELECT * FROM Users WHERE username=$1 AND password=$2`\nmsg.payload = [username, password]  // sql params\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":390,"y":1620,"wires":[["d163b1c99735e322","d6a5e85c9ca7735d"]]},{"id":"d163b1c99735e322","type":"debug","z":"54efb553244c241f","name":"debug 2","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":540,"y":1560,"wires":[]},{"id":"7ac97ab8768e5b00","type":"http response","z":"54efb553244c241f","name":"","statusCode":"","headers":{},"x":950,"y":1620,"wires":[]},{"id":"41315abeb16b886a","type":"http in","z":"54efb553244c241f","name":"","url":"/api","method":"get","upload":false,"swaggerDoc":"","x":200,"y":1800,"wires":[["36d167c6a4ed9bdd","967236e3fb342e56"]]},{"id":"36d167c6a4ed9bdd","type":"function","z":"54efb553244c241f","name":"verify JWT","func":"// FORMAT OF TOKEN\n// Authorization: Bearer <access_token>\n\nconst bearerHeader = msg.req.headers['authorization'];\n// Check if bearer is undefined\nif (typeof bearerHeader !== 'undefined') {\n    // Split at the space\n    const bearerToken = bearerHeader.split(' ')[1];\n    // Log the token in Debug window\n    node.warn({bearerToken});\n\n    // Verify Token\n    jwt.verify(bearerToken, 'secretkey', (err, authData) => {\n        if (err) {\n            msg.statusCode = \"403\";\n            msg.payload = err;\n            node.send(msg)\n        } else {\n            ///// API fake data /////\n            msg.payload = {\n                data: 'This is the data',\n                user: authData\n            };\n            node.send(msg)\n        }\n    });\n\n\n} else {\n    // Forbidden\n    msg.payload = \"Unauthorized\"\n    msg.statusCode = \"403\";\n    node.send(msg)\n}\n\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"jwt","module":"jsonwebtoken"}],"x":410,"y":1800,"wires":[["97b4bf6ee7c5794d","2ab0196e6418cd2a"]]},{"id":"97b4bf6ee7c5794d","type":"http response","z":"54efb553244c241f","name":"","statusCode":"","headers":{},"x":610,"y":1800,"wires":[]},{"id":"967236e3fb342e56","type":"debug","z":"54efb553244c241f","name":"debug 4","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":320,"y":1740,"wires":[]},{"id":"2ab0196e6418cd2a","type":"debug","z":"54efb553244c241f","name":"debug 5","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":520,"y":1740,"wires":[]},{"id":"d6a5e85c9ca7735d","type":"sqlite","z":"54efb553244c241f","mydb":"26cd2be8.1dfacc","sqlquery":"msg.topic","sql":"","name":"Sqlite","x":590,"y":1620,"wires":[["7fdf168306547292","5177c66563862a08"]]},{"id":"5177c66563862a08","type":"debug","z":"54efb553244c241f","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":1560,"wires":[]},{"id":"26cd2be8.1dfacc","type":"sqlitedb","db":"c:\\Users\\User\\.node-red\\EnergyMeters.db","mode":"RWC"}]

[EDIT]
As Julian said .. additional layers of security may be needed depending on the application. Like :

  1. hashing the passwords so they are not saved as clear text in the database
  2. using https so the data are sent encrypted between the server and client
  3. reverse proxy (rate limiting the requests, ip restrictions etc)