Can I use the Discourse API to authenticate users in another app?

I have a working discourse forum, where users can create a local login (username + password).

I’d like to reuse their login and password in another application. In other words: users would enter their username and password in the other application, and the application should be able to check whether this is a valid login for the forum.

I’ve been reading the documentation for the Discourse API. Many things are possible, including setting username and password for a given user, but I did not find an API endpoint to validate an existing username and password against the list of forum users.

I assume that such an API end point must exist, as the forum must be able to do this to login a user via the web interface.

What is the API end point to check a username and password to login to the forum?

2 Likes

More directly, you could use DiscourseConnect as a mechanism to validate users or out in front of your application, use discourse-auth-proxy.

These are suggested methods for authenticating users instead of handling login credentials directly. It also means you don’t need to try to handle 2FA details.

4 Likes

Actually, my “other application” is a desktop app, not a web app. I don’t think discourse-auth-proxy will work in that case.

On the DiscourseConnect page, one of the first statements is this:

Many sites wishing to integrate with a Discourse site want to keep all user registration in a separate site. In such a setup all login operations should be outsourced to that different site.

This is the exact opposite of what I want to do: I want to outsource all login operations to Discourse. Is there a way to use DiscourseConnect to do that?

1 Like

Yes, absolutely.

The tricky thing is that there is a shared secret between the provider (Discourse) and the consumer (your app). If you distribute your app, users will have access to all secrets in it.

Putting auth-proxy in front of a custom minimal web service that gives a signed token to your app might work well.

I’m sure there’s other ways of doing this I’m not thinking of.

Are you referring to the API key? It seems possible to create a “granular” API key, that has access to specific API end points only. It is still not clear to me which endpoints would be required, if I use that approach. Do you know?

Yes, a minimal web service with auth-proxy might be a good solution; I’ll have to experiment a bit to find out.

Not exactly - it would be the discourse connect provider secrets value for the application which would need to be set in conjuction with enable discourse connect provider.

More information on this is explained here: Use Discourse as an identity provider (SSO, DiscourseConnect)

1 Like

For a desktop app maybe this method used in this React Native example can be helpful for you:

https://github.com/pmusaraj/discourse-mobile-single-site-app/blob/main/js/Authenticate.js

1 Like

If I understand this correctly, this method would mean that the user logs in using a browser. That can work, although I was hoping to find a method where username and password can be entered in our desktop application, without opening a browser.

I understand that the approach I have in mind will not support TFA unless I implement it myself, and that it will not support logins via third-party providers (Google, Facebook, Discord, …)

I don’t believe this is explicitly supported, but I would create a session in the same manner the user would do in the web browser:


image

curl 'https://try.discourse.org/session' \
  -H 'sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"' \
  -H 'Discourse-Present: true' \
  -H 'DNT: 1' \
  -H 'X-CSRF-Token: …' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'User-Agent: …' \
  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H 'Accept: */*' \
  -H 'Referer: https://try.discourse.org/' \
  -H 'X-Requested-With: XMLHttpRequest' \
  -H 'sec-ch-ua-platform: "Linux"' \
  --data-raw 'login=demouser&password=demopassword&second_factor_method=1&timezone=America%2FToronto' \
  --compressed

and duplicate this login logic in the app.

As far as I understand at this moment, it looks like the method used in the Reactive Native example can be transposed to our desktop application (which is in Python).

The API access point used seems to be <site>/session, and it takes a username, password and a csrf token. The csrf token can be obtained from <site>/session/csrf.

This is very close to what I was looking for. I think I will try that, will report back if it works for me.

Is the <site>/session API access point documented anywhere?

1 Like

The best way to what you want in a Desktop app is using User API Keys.

You do need a web interface either in the app or by opening the browser, but if you make your app a handler for the protocol used by the mobile apps, you can easily have it get the token that way and only have to use the browser again if the token expires or they use a different device.

My personal experience with this is that using the User API keys is a much safer and simpler option than trying to use the session end points. :slight_smile:

2 Likes

Here are 20 lines of Python code that do approximately the same as the React Native code referred to by @renato (except no compatibiliy with Discourse 2.5 - I don’t need that)

It works well, assuming that you are using basic username-password based login. I will still look into the alternative methods, using the Discourse SSO login as configured in the Discourse instance.

import requests
import json

def discourse_authenticate(url, name, password):
    session = requests.Session()
    session.headers.update({'X-Requested-With': 'XMLHttpRequest'})
    r1 = session.get(url + '/session/csrf')
    csrf_token = json.loads(r1.text).get('csrf')
    r2 = session.post(url + '/session',
        data={
            'login': name,
            'password': password,
            'authenticity_token': csrf_token,
        },
    )
    if r2.status_code != 200:
        return None
    return json.loads(r2.text)

I’ve tried to apply this but can’t get it to work. Below is some (simplified) Python code that generates a url for .../session/sso_provider. When I try it, I get Login Error. No idea what that means.

import secrets
import base64
import urllib.parse
import hmac
import hashlib

forum_url = 'https://forum.embeetle.com'
target_url = 'https://embeetle.com/#account'
sso_secret = b'JCLSVcqbAnEPXz2p2xBY'

nonce = secrets.token_urlsafe()
payload = f'nonce={nonce}&return_sso_url={target_url}'
payload_base64 = base64.b64encode(payload.encode('utf-8')).decode()
payload_for_url = urllib.parse.quote(payload_base64)

payload_for_url = 'bm9uY2U9YklKeEU1WWw2OFhjSkJydGlwSU15UTRZeVlMeWd6ZzQyUU9mOFo0SWF5QSZyZXR1cm5fc3NvX3VybD1odHRwczovL2VtYmVldGxlLmNvbS8jYWNjb3VudA%3D%3D'

signature = hmac.new(
    sso_secret, payload_for_url.encode('utf-8'), hashlib.sha256
).hexdigest()

print(f'{forum_url}/session/sso_provider?sso={payload_for_url}&sig={signature}')

For example, one run might generate the url in the curl command below:

johan@morla:~/sa\> curl 'https://forum.embeetle.com/session/sso_provider?sso=bm9uY2U9YklKeEU1WWw2OFhjSkJydGlwSU15UTRZeVlMeWd6ZzQyUU9mOFo0SWF5QSZyZXR1cm5fc3NvX3VybD1odHRwczovL2VtYmVldGxlLmNvbS8jYWNjb3VudA%3D%3D&sig=a392ebb81b93ba7411290fbd00240921ae053bbb82998830dda994c8a71853da'
Login Errorjohan@morla:~/sa\> 

as admin, enable verbose discourse connect logging, try it, then check /logs on your forum to see more detailed errors, e.g. https://forum.embeetle.com/logs

You’ll see e.g.:

You need to sign the payload, not the quoted payload, e.g.:

signature = hmac.new(
  sso_secret, payload_base64.encode('utf-8'), hashlib.sha256
).hexdigest()

And then it works!

BTW, you need to revoke and change this secret immediately because anyone with it can log onto your app as I’ve just done while testing this.

2 Likes

Don’t worry, the page I am redirecting to is a public page, just for testing.

I changed the secret anyway.

2 Likes

What’s your top 3 suggestions on open-source to that specific task?

I’m using Nginx but probably that could be managed with a more robust development like Keycloak?

By auth-proxy, I mean this: GitHub - discourse/discourse-auth-proxy: An http proxy that uses the DiscourseConnect protocol to authenticate users

2 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.