Skip to content

Autenticazione SSO e OAuth

Configura l'accesso con Google, GitHub, HuggingFace e OIDC generico in Potato. Limita l'accesso per dominio email o organizzazione GitHub e usa l'SSO insieme all'accesso locale con password.

Novità in v2.3.0

Per impostazione predefinita, Potato usa un semplice accesso basato sul nome utente, in cui gli annotatori digitano un nome utente qualsiasi per entrare. Questo va bene per lo sviluppo locale e i piccoli progetti, ma i deployment in produzione hanno bisogno di un'autenticazione vera e propria per impedire accessi non autorizzati, confermare l'identità degli annotatori e soddisfare i requisiti istituzionali.

L'SSO si configura in un unico punto. Imposta authentication.method su oauth, poi elenca uno o più provider sotto authentication.providers. Ogni provider ha il proprio pulsante nella pagina di accesso.

Potato include i metadati degli endpoint per tre provider: google, github e huggingface. Qualsiasi altro nome di provider viene trattato come OIDC generico e richiede una discovery_url.

Avvio rapido

Una configurazione minima per Google:

yaml
authentication:
  method: "oauth"
  providers:
    google:
      client_id: ${GOOGLE_CLIENT_ID}
      client_secret: ${GOOGLE_CLIENT_SECRET}
  user_identity_field: "email"
 
secret_key: ${POTATO_SECRET_KEY}

Imposta i segreti nell'ambiente anziché nel file:

bash
export GOOGLE_CLIENT_ID="your-client-id.apps.googleusercontent.com"
export GOOGLE_CLIENT_SECRET="your-client-secret"
export POTATO_SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_hex(32))')"

secret_key è una chiave di primo livello e non fa parte del blocco authentication. Impostala, oppure imposta POTATO_SECRET_KEY. Se mancano entrambe, Potato genera una chiave casuale, il che significa che ogni riavvio invalida tutte le sessioni, e registra un avviso quando OAuth è configurato senza di essa.

Potato espone due route OAuth. /auth/login/<provider> avvia il flusso e /auth/callback/<provider> riceve la risposta. Il callback è l'URL che registri presso il provider, quindi per Google è https://your-domain.com/auth/callback/google. Non esiste una chiave di configurazione redirect_uri; la route deriva dal nome del provider.

Google

Crea credenziali OAuth 2.0 di tipo "Web application" nella Google Cloud Console e aggiungi https://your-domain.com/auth/callback/google come URI di reindirizzamento autorizzato. Per lo sviluppo locale è accettato http://localhost:8000/auth/callback/google.

yaml
authentication:
  method: "oauth"
  providers:
    google:
      client_id: ${GOOGLE_CLIENT_ID}
      client_secret: ${GOOGLE_CLIENT_SECRET}
      allowed_domain: "umich.edu"

allowed_domain accetta una singola stringa di dominio. Possono accedere solo gli account la cui email appartiene a quel dominio.

GitHub

Registra una OAuth app in GitHub Settings > Developer settings > OAuth Apps, con l'URL di callback impostato su https://your-domain.com/auth/callback/github.

yaml
authentication:
  method: "oauth"
  providers:
    github:
      client_id: ${GITHUB_CLIENT_ID}
      client_secret: ${GITHUB_CLIENT_SECRET}
      allowed_org: "my-research-lab"

allowed_org accetta un singolo nome di organizzazione. Quando è impostato, Potato richiede automaticamente lo scope read:org, quindi non devi elencare tu gli scope. L'annotatore deve avere un'appartenenza pubblica all'organizzazione perché il controllo la veda.

HuggingFace

Crea un'applicazione OAuth su huggingface.co/settings/applications.

yaml
authentication:
  method: "oauth"
  providers:
    huggingface:
      client_id: ${HF_CLIENT_ID}
      client_secret: ${HF_CLIENT_SECRET}
  user_identity_field: "username"

Il provider HuggingFace si collega all'endpoint di discovery OIDC di HuggingFace e richiede openid profile email per impostazione predefinita.

OIDC generico

Qualsiasi nome di provider diverso da google, github e huggingface viene trattato come OIDC generico e richiede discovery_url. Assegnagli un display_name perché il pulsante di accesso sia leggibile.

yaml
authentication:
  method: "oauth"
  providers:
    oidc:
      display_name: "University SSO"
      discovery_url: "https://sso.university.edu/.well-known/openid-configuration"
      client_id: ${OIDC_CLIENT_ID}
      client_secret: ${OIDC_CLIENT_SECRET}
      scopes:
        - "openid"
        - "email"
        - "profile"

L'URL di callback da registrare presso il provider segue la chiave del provider, quindi un blocco chiamato oidc usa https://your-domain.com/auth/callback/oidc.

URL di discovery dei provider più comuni:

ProviderURL di discovery
Oktahttps://your-org.okta.com/.well-known/openid-configuration
Azure ADhttps://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration
Auth0https://your-domain.auth0.com/.well-known/openid-configuration
Keycloakhttps://keycloak.example.com/realms/{realm}/.well-known/openid-configuration

Il valore predefinito di scopes è ["openid", "email", "profile"].

Più provider

providers accoglie tutte le voci che servono, e ognuna mostra il proprio pulsante di accesso.

yaml
authentication:
  method: "oauth"
  providers:
    google:
      client_id: ${GOOGLE_CLIENT_ID}
      client_secret: ${GOOGLE_CLIENT_SECRET}
    github:
      client_id: ${GITHUB_CLIENT_ID}
      client_secret: ${GITHUB_CLIENT_SECRET}
    oidc:
      display_name: "University SSO"
      discovery_url: "https://sso.university.edu/.well-known/openid-configuration"
      client_id: ${OIDC_CLIENT_ID}
      client_secret: ${OIDC_CLIENT_SECRET}

Con user_identity_field: "email", chi accede tramite Google e in seguito tramite il provider dell'università con lo stesso indirizzo è lo stesso utente Potato e ritrova le stesse assegnazioni.

Identità e registrazione automatica

user_identity_field stabilisce quale claim del provider diventa il nome utente Potato. Accetta email, username, sub o name, e il valore predefinito è email. Il valore conta perché le directory di output degli annotatori prendono il nome dall'utente, quindi cambiarlo a metà studio separa un annotatore dal lavoro svolto in precedenza.

auto_register controlla se il primo accesso riuscito crea l'account Potato. Il valore predefinito è true.

yaml
authentication:
  method: "oauth"
  providers:
    google:
      client_id: ${GOOGLE_CLIENT_ID}
      client_secret: ${GOOGLE_CLIENT_SECRET}
      allowed_domain: "umich.edu"
  user_identity_field: "email"
  auto_register: true

Il controllo degli accessi passa dal provider e non da un elenco di indirizzi nella configurazione. Per limitare chi può accedere, usa allowed_domain per Google, allowed_org per GitHub, oppure i controlli sui gruppi e sull'assegnazione delle app del tuo provider OIDC.

Modalità mista

allow_local_login aggiunge il modulo con nome utente e password sotto i pulsanti SSO. Il valore predefinito è false, quindi l'SSO sostituisce l'accesso locale a meno che tu non lo attivi.

yaml
require_password: true
 
user_config:
  allow_all_users: true
 
authentication:
  method: "oauth"
  providers:
    google:
      client_id: ${GOOGLE_CLIENT_ID}
      client_secret: ${GOOGLE_CLIENT_SECRET}
  allow_local_login: true

Durante una migrazione i due convivono per un periodo. Sposta gli annotatori sul provider, poi rimuovi allow_local_login quando tutti hanno effettuato l'accesso tramite il provider. La modalità mista copre anche gli studi che uniscono annotatori dell'istituzione ad annotatori esterni che non hanno un account presso la tua istituzione.

Gli account locali in modalità mista usano lo stesso hashing PBKDF2 con salt di qualsiasi altro account con password. Consulta Gestione delle password per le procedure di reimpostazione.

Sessioni

La durata della sessione è una chiave di primo livello, espressa in giorni:

yaml
session_lifetime_days: 7

È una secret_key stabile a far sopravvivere le sessioni a un riavvio.

Accesso amministratore

Gli endpoint di amministrazione si autenticano con una chiave API anziché tramite il blocco OAuth:

yaml
admin_api_key: ${POTATO_ADMIN_API_KEY}

Potato legge admin_api_key prima dalla configurazione, poi dalla variabile d'ambiente POTATO_ADMIN_API_KEY. Se nessuna delle due è impostata, genera una chiave e la scrive in admin_api_key.txt nella directory del task. Chi chiama la presenta nell'header X-API-Key.

HTTPS

In produzione i provider OAuth richiedono URI di reindirizzamento HTTPS. localhost è esente, quindi lo sviluppo su HTTP funziona senza certificato.

Dietro un reverse proxy, inoltra lo schema originale, altrimenti l'URL di callback costruito da Potato userà http e il provider lo rifiuterà:

nginx
location / {
    proxy_pass http://localhost:8000;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
}

Riferimento di configurazione

ChiaveTipoPredefinitoDescrizione
authentication.methodstringin_memoryin_memory, database, clerk o oauth
authentication.providersdictobbligatorio per oauthUna voce per provider, con il nome del provider come chiave
authentication.user_identity_fieldstringemailemail, username, sub o name
authentication.allow_local_loginboolfalseMostra il modulo della password accanto ai pulsanti SSO
authentication.auto_registerbooltrueCrea l'account Potato al primo accesso
secret_keystringcasualeDi primo livello. Serve un valore stabile per sessioni persistenti
session_lifetime_daysint7Durata della sessione, di primo livello
admin_api_keystringgenerataChiave di primo livello per gli endpoint di amministrazione

Chiavi per provider:

ChiaveTipoObbligatoriaDescrizione
client_idstringsìFornito dal provider
client_secretstringsìFornito dal provider
discovery_urlstringper OIDC genericoEndpoint di discovery OIDC
display_namestringnoTesto del pulsante di accesso
allowed_domainstringnoLimita a un dominio email
allowed_orgstringnoLimita a un'organizzazione GitHub
scopeslistnoPredefinito ["openid", "email", "profile"]

Esempio completo

yaml
annotation_task_name: "Annotation Project"
task_dir: "."
 
authentication:
  method: "oauth"
  providers:
    google:
      client_id: ${GOOGLE_CLIENT_ID}
      client_secret: ${GOOGLE_CLIENT_SECRET}
      allowed_domain: "umich.edu"
  user_identity_field: "email"
  auto_register: true
 
secret_key: ${POTATO_SECRET_KEY}
session_lifetime_days: 7
admin_api_key: ${POTATO_ADMIN_API_KEY}
 
data_files:
  - "data/instances.jsonl"
 
item_properties:
  id_key: id
  text_key: text
 
annotation_schemes:
  - annotation_type: radio
    name: sentiment
    description: "What is the sentiment of this text?"
    labels: [Positive, Neutral, Negative]
 
output_annotation_dir: "output/"
export_annotation_format: "jsonl"

Risoluzione dei problemi

Il provider rifiuta l'URI di reindirizzamento. L'URI registrato presso il provider deve corrispondere esattamente a /auth/callback/<provider>. Lo schema e qualsiasi percorso finale contano ai fini della corrispondenza, quindi un proxy che non inoltra X-Forwarded-Proto produce un callback http che il provider rifiuta.

Dopo un riavvio tutti risultano disconnessi. secret_key non è impostata, quindi Potato ne ha generata una nuova all'avvio.

Una restrizione per organizzazione GitHub respinge un membro. La sua appartenenza all'organizzazione è privata. O la rende pubblica, o rimuovi allowed_org e controlli l'accesso in un altro modo.

La validazione fallisce con "providers is required". method: "oauth" richiede almeno una voce sotto providers. Il valore del metodo è oauth, non il nome di un provider.


Ulteriori letture

Per i dettagli di implementazione, consulta la documentazione sorgente.