Posso usare l'API di Discourse per autenticare gli utenti in un'altra app?

Ho un forum Discourse funzionante, dove gli utenti possono creare un accesso locale (nome utente + password).

Vorrei riutilizzare il loro nome utente e password in un’altra applicazione. In altre parole: gli utenti inserirebbero il loro nome utente e password nell’altra applicazione, e l’applicazione dovrebbe essere in grado di verificare se si tratta di un accesso valido per il forum.

Ho letto la documentazione per l’API di Discourse. Molte cose sono possibili, inclusa l’impostazione del nome utente e della password per un dato utente, ma non ho trovato un endpoint API per convalidare un nome utente e password esistenti rispetto all’elenco degli utenti del forum.

Presumo che un endpoint API del genere debba esistere, poiché il forum deve essere in grado di farlo per accedere a un utente tramite l’interfaccia web.

Qual è l’endpoint API per verificare nome utente e password per accedere al forum?

3 Mi Piace

Più direttamente, potresti usare DiscourseConnect come meccanismo per convalidare gli utenti o, prima della tua applicazione, usare discourse-auth-proxy.

Questi sono metodi suggeriti per autenticare gli utenti invece di gestire direttamente le credenziali di accesso. Significa anche che non devi preoccuparti di gestire i dettagli della 2FA.

5 Mi Piace

In realtà, la mia “altra applicazione” è un’app desktop, non un’app web. Non credo che discourse-auth-proxy funzionerà in quel caso.

Nella pagina DiscourseConnect, una delle prime affermazioni è questa:

Molti siti che desiderano integrarsi con un sito Discourse vogliono mantenere tutte le registrazioni degli utenti su un sito separato. In una configurazione del genere, tutte le operazioni di accesso dovrebbero essere esternalizzate a quel sito diverso.

Questo è l’esatto opposto di ciò che voglio fare: voglio esternalizzare tutte le operazioni di accesso a Discourse. Esiste un modo per utilizzare DiscourseConnect per fare ciò?

1 Mi Piace

Sì, assolutamente.

La cosa complicata è che c’è un segreto condiviso tra il provider (Discourse) e il consumer (la tua app). Se distribuisci la tua app, gli utenti avranno accesso a tutti i segreti al suo interno.

Mettere un proxy di autenticazione davanti a un servizio web minimale personalizzato che fornisce un token firmato alla tua app potrebbe funzionare bene.

Sono sicuro che ci sono altri modi per farlo a cui non sto pensando.

Ti riferisci alla chiave API? Sembra possibile creare una chiave API “granulare”, che abbia accesso solo a specifici endpoint API. Non mi è ancora chiaro quali endpoint sarebbero necessari, se usassi quell’approccio. Lo sai?

Sì, un servizio web minimo con un proxy di autenticazione potrebbe essere una buona soluzione; dovrò sperimentare un po’ per scoprirlo.

Non esattamente: sarebbe il valore discourse connect provider secrets per l’applicazione che dovrebbe essere impostato in congiunzione con enable discourse connect provider.

Maggiori informazioni sono spiegate qui: Use Discourse as an identity provider (SSO, DiscourseConnect)

1 Mi Piace

Per un’app desktop, forse questo metodo utilizzato in questo esempio di React Native può esserti utile:

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

1 Mi Piace

Se ho capito bene, questo metodo significherebbe che l’utente accede tramite un browser. Può funzionare, anche se speravo di trovare un metodo in cui nome utente e password potessero essere inseriti nella nostra applicazione desktop, senza aprire un browser.

Capisco che l’approccio che ho in mente non supporterà l’autenticazione a due fattori (TFA) a meno che non la implementi io stesso, e che non supporterà gli accessi tramite provider di terze parti (Google, Facebook, Discord, …).

Non credo che questo sia esplicitamente supportato, ma creerei una sessione nello stesso modo in cui l’utente farebbe nel browser web:


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

e duplicherei questa logica di accesso nell’app.

Per quanto ne capisco al momento, sembra che il metodo utilizzato nell’esempio di Reactive Native possa essere trasposto alla nostra applicazione desktop (che è in Python).

Il punto di accesso API utilizzato sembra essere <site>/session, e accetta un nome utente, una password e un token csrf. Il token csrf può essere ottenuto da <site>/session/csrf.

Questo è molto vicino a quello che stavo cercando. Penso che proverò così, vi farò sapere se funziona per me.

Il punto di accesso API <site>/session è documentato da qualche parte?

1 Mi Piace

Il modo migliore per ottenere ciò che desideri in un’app desktop è utilizzare le User API Keys.

È necessaria un’interfaccia web, nell’app o aprendo il browser, ma se rendi la tua app un gestore per il protocollo utilizzato dalle app mobili, puoi facilmente ottenere il token in quel modo e dover usare il browser solo di nuovo se il token scade o se utilizzano un dispositivo diverso.

La mia esperienza personale con questo è che l’utilizzo delle User API keys è un’opzione molto più sicura e semplice rispetto al tentativo di utilizzare gli endpoint di sessione. :slight_smile:

2 Mi Piace

Ecco 20 righe di codice Python che fanno approssimativamente la stessa cosa del codice React Native a cui si riferisce @renato (tranne che non c’è compatibilità con Discourse 2.5 - non ne ho bisogno)

Funziona bene, assumendo che tu stia utilizzando l’autenticazione di base basata su nome utente e password. Esaminerò ancora i metodi alternativi, utilizzando l’autenticazione SSO di Discourse come configurato nell’istanza di Discourse.

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)

Ho provato ad applicarlo ma non riesco a farlo funzionare. Di seguito è riportato un codice Python (semplificato) che genera un URL per .../session/sso_provider. Quando ci provo, ottengo Login Error. Non ho idea di cosa significhi.

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}')

Ad esempio, un’esecuzione potrebbe generare l’URL nel comando curl sottostante:

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

come amministratore, abilita verbose discourse connect logging, provalo, quindi controlla /logs sul tuo forum per vedere errori più dettagliati, ad es. https://forum.embeetle.com/logs

Vedrai ad es.:

Devi firmare il payload, non il payload quotato, ad es.:

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

E poi funziona!

A proposito, devi revocare e cambiare immediatamente questo segreto perché chiunque lo abbia può accedere alla tua app come ho appena fatto durante il test.

3 Mi Piace

Non preoccuparti, la pagina a cui sto reindirizzando è una pagina pubblica, solo per test.

Ho comunque cambiato il segreto.

2 Mi Piace

Quali sono i tuoi primi 3 suggerimenti open-source per quel compito specifico?

Sto usando Nginx ma probabilmente questo potrebbe essere gestito con uno sviluppo più robusto come Keycloak?

Con auth-proxy, intendo questo: GitHub - discourse/discourse-auth-proxy: An http proxy that uses the DiscourseConnect protocol to authenticate users

2 Mi Piace

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