Authenticating to FarmOS from Node-Red

@Farmer-Ed
When trying out your flows, you use a different way of authenticating to FarmOS than I’ve used till now. I used the dashboard login form.

Your flows use the credentials node with some strings I’m not sure about.
How do I find my cliend_id, client_secret, and scope?

1 Like

Don’t know what level of access you have with Farmier but theses are all set under Webservices Consumer Entities. I assume if you’ve not had to include them so far then the Authentication flow you are using has the default ones hardcoded.

client = farm
client_secret = client_secret (may actually be blank by default but perhaps the flow expects a value)
scope = farm_manager / farm_worker / farm_viewer

I assume you are using this one?

if you look at Create OAuth2Request you will see what you are using now.

var farmOSurl = msg.payload.url
global.set('farmOSurl', farmOSurl);

msg.oauth2Request = { 
        "access_token_url": msg.payload.url.concat("/oauth/token"), 
        "credentials": {
            "grant_type": "password",
            "username": msg.payload.username,
            "password": msg.payload.password,
            "client_id": "farm",
            "client_secret": "client_secret",
            "scope": "farm_manager"
        }
    };
return msg;

I may make this simpler in the next version of my Authentication node by combining the Credentials node and maybe include the defaults while leaving them editable.

Ah… Tanks for clarifying this.
Did’nt think of the obvious… :flushed:
The Create OAuth2Request node has all the info. Have not tested yet, but I bet it works.

Maybe I don’t need the login form at all.
But a centralized config node for credentials could perhaps be an idea. Easier if one would change the password. Maybe I’ll try that some day.

1 Like

No Problem, sometimes its hard to see the obvious.

The login form can be useful too, but it depends on your use case.

There is probably plenty that could be done to make a few more simplified nodes and maybe a few additional ones for specific tasks. I am open to a bit of collaboration there if you have any ideas. Sub-flows can be packaged up as nodes which can make developing straight forward.

1 Like

Awesome. I think basic usage should be as easy as possible to lower the learning curve for new users. I’m planning on some more flows for myself first, and then expand knowledge a bit.
(Winter-time is nerding-time :nerd_face: )

1 Like

Hi has anyone had issues with Node-Red Authentication in the lastest farmOS update? I keep getting a client_id error.

1 Like

I am using farmerEd’s Library show in the above posts. Here is the error.

6/18/2024, 10:10:34 PMnode: 7f2aea8f0c768b78msg.payload : string[14]

“invalid_client”

1 Like

Hi @Zack did you have it working previously on any version of 3.x ?

I may be a minor release behind the latest but it is working for me. “invalid_client” would suggest your client id does not match what is defined in your consumer configuration.

1 Like

@Zack can you post the whole payload? If I remember correctly you noticed that it was not setting client_id at all, is that correct? Is that apparent in the payload?

For what it’s worth, I manually tested making API requests to @Zack’s instance via curl, and they worked as expected. So it seems to be something with the Node Red configuration. It seems to have started happening when I upgraded @Zack to v3.2.2 - although I’m not aware of any code changes that would affect it (and the fact that curl still works is odd).

1 Like

Weird if it is only v3.2.2 ??

@zack, what version of the Nodes are you using? was it a library installed with the palette/npm or just some of my subflows imported as JSON.

You could post your Authentication flow here Removing any credentials and I will take a look. I can try it on my current instance and again after I upgrade to latest (“possibly over the weekend”).

Also if you change the debug output from msg.payload to complete msg object you may get a more detailed description of the error (or possibly just msg.error)

1 Like

Here is my node red code.

Here is the complete msg. Not much more info.

" 6/19/2024, 9:25:41 AMnode: d1141bbe2330f012msg : Object

object

_msgid: “094afa1d420d40db”

payload: “invalid_client”

client_secret: “client_secret”

error: object

error: “invalid_client”

error_description: “Client authentication failed”

message: “Client authentication failed”
"

[
    {
        "id": "01a260c82115e7f0",
        "type": "tab",
        "label": "Flow 1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "4312767a46852e41",
        "type": "inject",
        "z": "01a260c82115e7f0",
        "name": "",
        "props": [],
        "repeat": "3480",
        "crontab": "",
        "once": true,
        "onceDelay": "1",
        "topic": "",
        "x": 150,
        "y": 120,
        "wires": [
            [
                "56a09a4dcf90dc67"
            ]
        ]
    },
    {
        "id": "d1141bbe2330f012",
        "type": "debug",
        "z": "01a260c82115e7f0",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": true,
        "tostatus": true,
        "complete": "true",
        "targetType": "full",
        "statusVal": "payload",
        "statusType": "auto",
        "x": 675,
        "y": 120,
        "wires": []
    },
    {
        "id": "56a09a4dcf90dc67",
        "type": "credentials",
        "z": "01a260c82115e7f0",
        "name": "farmOS Credentials",
        "props": [
            {
                "value": "payload.url",
                "type": "msg"
            },
            {
                "value": "payload.username",
                "type": "msg"
            },
            {
                "value": "payload.password",
                "type": "msg"
            },
            {
                "value": "payload.client_id",
                "type": "msg"
            },
            {
                "value": "client_secret",
                "type": "msg"
            },
            {
                "value": "payload.scope",
                "type": "msg"
            }
        ],
        "x": 330,
        "y": 120,
        "wires": [
            [
                "3f82b9aad49dae49"
            ]
        ]
    },
    {
        "id": "3f82b9aad49dae49",
        "type": "sf:895a366cbeeeb85e",
        "z": "01a260c82115e7f0",
        "name": "",
        "x": 520,
        "y": 120,
        "wires": [
            [
                "d1141bbe2330f012"
            ]
        ]
    }
]
2 Likes

oh…
That’s an old one, I haven’t used it in a long while.

Try this one:

[
    {
        "id": "cf0290052fc9e037",
        "type": "subflow",
        "name": "farmOS Oauth2V2",
        "info": "",
        "category": "farmOS V2",
        "in": [
            {
                "x": 60,
                "y": 260,
                "wires": [
                    {
                        "id": "acfc46e21bdf62c6"
                    }
                ]
            }
        ],
        "out": [
            {
                "x": 1230,
                "y": 260,
                "wires": [
                    {
                        "id": "3d0bb775fbb6d738",
                        "port": 0
                    }
                ]
            },
            {
                "x": 950,
                "y": 120,
                "wires": [
                    {
                        "id": "9eb02a196b5e91bd",
                        "port": 0
                    }
                ]
            }
        ],
        "env": [
            {
                "name": "grant",
                "type": "str",
                "value": "client",
                "ui": {
                    "icon": "font-awesome/fa-id-card-o",
                    "label": {
                        "en-US": "Grant Type"
                    },
                    "type": "select",
                    "opts": {
                        "opts": [
                            {
                                "l": {
                                    "en-US": "authorization code"
                                },
                                "v": "code"
                            },
                            {
                                "l": {
                                    "en-US": "client credentials"
                                },
                                "v": "client"
                            },
                            {
                                "l": {
                                    "en-US": "password"
                                },
                                "v": "password"
                            }
                        ]
                    }
                }
            },
            {
                "name": "scope",
                "type": "str",
                "value": "farm_worker",
                "ui": {
                    "type": "select",
                    "opts": {
                        "opts": [
                            {
                                "l": {
                                    "en-US": "Manager"
                                },
                                "v": "farm_manager"
                            },
                            {
                                "l": {
                                    "en-US": "Viewer"
                                },
                                "v": "farm_viewer"
                            },
                            {
                                "l": {
                                    "en-US": "Worker"
                                },
                                "v": "farm_worker"
                            }
                        ]
                    }
                }
            },
            {
                "name": "url",
                "type": "str",
                "value": ""
            },
            {
                "name": "redirect",
                "type": "str",
                "value": ""
            },
            {
                "name": "client",
                "type": "cred"
            },
            {
                "name": "secret",
                "type": "cred"
            },
            {
                "name": "username",
                "type": "cred"
            },
            {
                "name": "password",
                "type": "cred"
            }
        ],
        "meta": {},
        "color": "#C7E9C0",
        "outputLabels": [
            "Debug",
            "Autherization Code URL"
        ],
        "icon": "node-red-contrib-oauth2/oauth2.svg"
    },
    {
        "id": "ce468c0ef9940f5f",
        "type": "function",
        "z": "cf0290052fc9e037",
        "name": "authorization_code",
        "func": "var farmOSurl = env.get('url');\nglobal.set('farmOSurl', farmOSurl);\n\nvar client = env.get('client');\nvar redirect = env.get('redirect');\nvar secret = env.get('secret');\nvar scope = env.get('scope');\n\nmsg.payload ={\n        \"grant_type\": \"authorization_code\",\n            \"client_id\": client,\n            \"code\": msg.payload.code,\n            \"state\": msg.payload.state,\n            \"redirect_uri\": redirect,\n            \"client_secret\": secret,\n            \"scope\": scope\n        }\n\nmsg.headers = {};\nmsg.headers['Accept'] = \"application/json\";\nmsg.headers['content-type'] = 'application/x-www-form-urlencoded';\n\nmsg.method = \"POST\";\nmsg.url = env.get('url') + \"/oauth/token\";\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 390,
        "y": 180,
        "wires": [
            [
                "9210259c7f2b215c"
            ]
        ]
    },
    {
        "id": "9210259c7f2b215c",
        "type": "http request",
        "z": "cf0290052fc9e037",
        "name": "",
        "method": "use",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "",
        "tls": "bec8412e2fdbd417",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 650,
        "y": 260,
        "wires": [
            [
                "4b6637a23189e0ac"
            ]
        ]
    },
    {
        "id": "33606295cfc54171",
        "type": "function",
        "z": "cf0290052fc9e037",
        "name": "client_credentials",
        "func": "var farmOSurl = env.get('url');\nglobal.set('farmOSurl', farmOSurl);\n\nvar client = env.get('client');\nvar secret = env.get('secret');\nvar scope = env.get('scope');\n\nmsg.payload = {\n    \"grant_type\": \"client_credentials\",\n                \"client_id\": client,\n                \"client_secret\": secret,\n                \"scope\": scope\n}\n\nmsg.headers = {};\nmsg.headers['Accept'] = \"application/json\";\nmsg.headers['content-type'] = 'application/x-www-form-urlencoded';\n\nmsg.method = \"POST\";\nmsg.url = env.get('url') + \"/oauth/token\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 390,
        "y": 260,
        "wires": [
            [
                "9210259c7f2b215c"
            ]
        ]
    },
    {
        "id": "acfc46e21bdf62c6",
        "type": "switch",
        "z": "cf0290052fc9e037",
        "name": "",
        "property": "grant",
        "propertyType": "env",
        "rules": [
            {
                "t": "eq",
                "v": "code",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "client",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "password",
                "vt": "str"
            }
        ],
        "checkall": "false",
        "repair": true,
        "outputs": 3,
        "x": 190,
        "y": 260,
        "wires": [
            [
                "5d16f61bb9acef3e"
            ],
            [
                "33606295cfc54171"
            ],
            [
                "3c15790622ac845b"
            ]
        ]
    },
    {
        "id": "3c15790622ac845b",
        "type": "function",
        "z": "cf0290052fc9e037",
        "name": "password",
        "func": "var farmOSurl = env.get('url');\nglobal.set('farmOSurl', farmOSurl);\n\nvar client = env.get('client');\nvar scope = env.get('scope');\nvar username = env.get('username');\nvar password = env.get('password');\n\nmsg.payload = {\n    \"grant_type\": \"password\",\n    \"username\": username,\n    \"password\": password,\n    \"client_id\": client,\n    \"scope\": scope\n}\n\nmsg.headers = {};\nmsg.headers['Accept'] = \"application/json\";\nmsg.headers['content-type'] = 'application/x-www-form-urlencoded';\n\nmsg.method = \"POST\";\nmsg.url = env.get('url') + \"/oauth/token\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 380,
        "y": 320,
        "wires": [
            [
                "9210259c7f2b215c"
            ]
        ]
    },
    {
        "id": "9eb02a196b5e91bd",
        "type": "function",
        "z": "cf0290052fc9e037",
        "name": "Generate  URL",
        "func": "msg.payload = env.get(\"url\") + \"/oauth/authorize?response_type=code&client_id=\" + env.get(\"client\") + \"&scope=\" + env.get(\"scope\") + \"&redirect_uri=\" + env.get(\"redirect\") + \"&state=p4W8P5f7gJCIDbC1Mv78zHhlpJOidy\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 380,
        "y": 120,
        "wires": [
            []
        ]
    },
    {
        "id": "5d16f61bb9acef3e",
        "type": "switch",
        "z": "cf0290052fc9e037",
        "name": "",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "url",
                "vt": "str"
            },
            {
                "t": "hask",
                "v": "code",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 190,
        "y": 140,
        "wires": [
            [
                "9eb02a196b5e91bd"
            ],
            [
                "ce468c0ef9940f5f"
            ]
        ]
    },
    {
        "id": "4b6637a23189e0ac",
        "type": "function",
        "z": "cf0290052fc9e037",
        "name": "Process tokens",
        "func": "var time;\nif (msg.payload.access_token) {\n    var token = msg.payload.access_token;\n    var refresh = msg.payload.refresh_token;\n    var expiry = Date.now() + (msg.payload.expires_in * 1000) - 120000;\n    global.set('farmos_token_expiry', expiry);\n    global.set('token', token);\n    global.set('farmos_refresh', refresh);\n    time = new Date();\n    msg.bearer = msg.payload;\n    msg.payload = \"Access Granted \" + time;\n} else {\n    msg.error = msg.payload;\n    msg.payload = msg.error.error;\n}\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 860,
        "y": 260,
        "wires": [
            [
                "3d0bb775fbb6d738"
            ]
        ]
    },
    {
        "id": "3d0bb775fbb6d738",
        "type": "change",
        "z": "cf0290052fc9e037",
        "name": "",
        "rules": [
            {
                "t": "delete",
                "p": "url",
                "pt": "msg"
            },
            {
                "t": "delete",
                "p": "method",
                "pt": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 1060,
        "y": 260,
        "wires": [
            []
        ]
    },
    {
        "id": "bec8412e2fdbd417",
        "type": "tls-config",
        "z": "cf0290052fc9e037",
        "name": "",
        "cert": "",
        "key": "",
        "ca": "",
        "certname": "",
        "keyname": "",
        "caname": "",
        "servername": "",
        "verifyservercert": false,
        "alpnprotocol": ""
    },
    {
        "id": "dc37d527c32af077",
        "type": "inject",
        "z": "0bd8a383a9bfc898",
        "name": "",
        "props": [
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 150,
        "y": 200,
        "wires": [
            [
                "c9a5e48f9887c6ab"
            ]
        ]
    },
    {
        "id": "340cf27d9c9452f8",
        "type": "debug",
        "z": "0bd8a383a9bfc898",
        "name": "debug 2",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 600,
        "y": 200,
        "wires": []
    },
    {
        "id": "c9a5e48f9887c6ab",
        "type": "subflow:cf0290052fc9e037",
        "z": "0bd8a383a9bfc898",
        "name": "",
        "env": [
            {
                "name": "grant",
                "value": "password",
                "type": "str"
            },
            {
                "name": "scope",
                "value": "farm_manager",
                "type": "str"
            },
            {
                "name": "url",
                "value": "https://your_farmOS_address",
                "type": "str"
            },
            {
                "name": "redirect",
                "value": "https://ignore_unless_using_authorization_code",
                "type": "str"
            },
            {
                "name": "client",
                "type": "cred"
            },
            {
                "name": "secret",
                "type": "cred"
            },
            {
                "name": "username",
                "type": "cred"
            },
            {
                "name": "password",
                "type": "cred"
            }
        ],
        "x": 370,
        "y": 200,
        "wires": [
            [
                "340cf27d9c9452f8"
            ],
            []
        ]
    }
]
1 Like

I believe this instance was through NPM. I did do some testing with another instance where I imported the JSON to see what your sub flow was doing. I still got the same error with it. It looked like it was functioning properly but could not see what the Oauth2 node was doing. I think the issue might be in it.

I’d notice this in the node red cmd terminal. Looks like the body isnt includeng client_id:

  _events: [Object: null prototype],
  _eventsCount: 3,
  _maxListeners: undefined,
  method: 'POST',
  headers: [Object],
  callback: [Function (anonymous)],
  readable: true,
  writable: true,
  explicitMethod: true,
  _qs: [Querystring],
  _auth: [Auth],
  _oauth: [OAuth],
  _multipart: [Multipart],
  _redirect: [Redirect],
  _tunnel: [Tunnel],
  setHeader: [Function (anonymous)],
  hasHeader: [Function (anonymous)],
  getHeader: [Function (anonymous)],
  removeHeader: [Function (anonymous)],
  localAddress: undefined,
  pool: [Object],
  dests: [],
  __isRequestRequest: true,
  _callback: [Function (anonymous)],
  uri: [Url],
  proxy: null,
  tunnel: true,
  setHost: true,
  originalCookieHeader: undefined,
  _disableCookies: true,
  _jar: undefined,
  port: 443,
  host: 'forestlanefarm.farmos.net',
  body: 'grant_type=password&scope=farm_worker&username=****&password=******',
  path: null,
  httpModule: [Object],
  agentClass: [Function: Agent],
  agent: [Agent],
  _started: true,
  href: 'https://forestlanefarm.farmos.net/oauth/token',
  _defaultAgent: [Agent],
  noDelay: true,
  servername: 'forestlanefarm.farmos.net',
  _agentKey: 'forestlanefarm.farmos.net:443::::::::true:::::::::::::',
  encoding: null,
  singleUse: true
}
1 Like

That worked! thanks!

2 Likes

The one you shared in the last post and the one available on NPM depend on a 3rd party Oauth2 Node which may be the issue if they have made a breaking change. The JSON I just sent you relies only on http requests.

1 Like

Deadly, guess I should release a new version to NPM soon so.
Glad you are sorted @Zack!

2 Likes

Wow! So I wonder if the update to 3.2.2 was unrelated? Or maybe there were multiple variables at play.

Glad it’s all working again for you either way @Zack!

2 Likes

I’m not sure, I’d need to setup a local test instance to test it out properly and don’t have the time at the moment as I have to go make some hay. But it needs updating on my part either way.

2 Likes