Logging in programmatically; Using access token from read API with write API plugin?
-
I'm working on a client app for NodeBB. Authenticating against the read API is simple enough, but I can't figure out how to use the resulting token with the write API. I understand that I can generate tokens for the write API from the ACP, but my goal is to issue an access token when a user logs in with his or her credentials, without having to generate a token manually for every user of my forum. Can anyone point me in the right direction? Thanks in advance.
For reference, this is what I'm doing now:
- start new session
- get csrf token from /api/config
- send POST request to /login with 'username' and 'password' as data and csrf token in header as 'x-csrf-token'
- receive session token with express.sid and io cookies (?)
-
Looking at the write-api, the create token call requires an exiting token, so there doesn't appear to be an automated way to generate one. The write-api would need to be modified to verify the currently logged in user, instead of the bearer token, in this case.
-
Shouldn't your client app, have the token on your code, to act as a consumer of the API?
-
The csrf token is not really anything that contains identifying information, as it's just there to ensure that repeat attacks are mitigated.
As @yariplus says, I'll have to modify the write API plugin to allow users to generate a token via the write API...
-
@julian I understand re: the csrf token—as far as I can tell, the express.sid cookie is used for authorization after the user logs in. I'm able to log in and successfully use the read API.
In lieu of using the write API in the way that I wanted to, I assume that I should be able to use websockets as the web client does, but I'm having trouble working out exactly how to do so.
On my end, this is what looks to be occurring:
- client gets csrf token from /api/config
- client performs GET request to /socket.io/?EIO=3&transport=polling
- client is issued 'io' cookie (related to websocket?) and express.sid cookie—I assume this is a session cookie
- client sends POST request to /login with the csrf token in the header, username and password in the request body, all of the relevant cookies
- HTTP 200 response
- user is then logged in and subsequent requests sent with cookies work as expected; can access private pages, etc.
Regarding websocket connections:
- 'io' cookie is used as 'sid' in query string to open an initial websocket: /socket.io/?EIO=3&transport=websocket&sid=[io cookie here]
- client seems to send payload containing 2probe
- server responds with 3probe
- client sends 5
- server sends 42["checkSession",1], which appears to indicate that the 'io' cookie has changed
- a new websocket is then opened using this new io cookie as sid
However, using Python's websocket-client to test, I don't see where the new 'io' cookie is actually issued or how to open the subsequent websocket. Also, I seem to have overlooked an additional query parameter for /socket.io/, t, which, even though I seem to be able to establish the initial websocket without it, appears to be completely random and to change at each stage.
Is there a library I should be using to negotiate this handshake?
Thanks for your responses and your time, everyone.
-
Reporting back. Using existing libraries, this is much simpler than I was making it. Here's some sample Python code which posts "Hello, World!" to topic ID 2. Thanks, @julian, for telling me about socket.io.
from socketIO_client import SocketIO, LoggingNamespace import requests import json session = requests.Session() csrf_token = json.loads(session.get('http://yourdomain:port/api/config').text)['csrf_token'] headers = { 'x-csrf-token': csrf_token } data = { "username": "yourUsername", "password": "yourPassword" } response = session.post("http://yourdomain:port/login", headers=headers, data=data) def on_response(*args): print('on_response', json.dumps(args)) with SocketIO('yourdomain', port, LoggingNamespace, cookies=session.cookies.get_dict()) as s: s.emit('posts.reply', {'tid': 2, 'content': "Hello, World!"}, on_response) s.wait_for_callbacks(seconds=1)