Skip to content

Authentification SSO et OAuth

Configurez la connexion Google, GitHub, HuggingFace et OIDC générique dans Potato. Restreignez l'accès par domaine de messagerie ou organisation GitHub, et utilisez le SSO avec la connexion locale par mot de passe.

Nouveau dans v2.3.0

Par défaut, Potato utilise une connexion simple par nom d'utilisateur, où les annotateurs saisissent n'importe quel nom d'utilisateur pour se connecter. Cela convient au développement local et aux petits projets, mais les déploiements en production ont besoin d'une véritable authentification pour empêcher les accès non autorisés, confirmer l'identité des annotateurs et répondre aux exigences institutionnelles.

Le SSO se configure à un seul endroit. Définissez authentication.method sur oauth, puis listez un ou plusieurs fournisseurs sous authentication.providers. Chaque fournisseur a son propre bouton sur la page de connexion.

Potato intègre les métadonnées d'endpoints de trois fournisseurs : google, github et huggingface. Tout autre nom de fournisseur est traité comme de l'OIDC générique et nécessite une discovery_url.

Démarrage rapide

Une configuration Google minimale :

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}

Définissez les secrets dans l'environnement plutôt que dans le fichier :

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 est une clé de premier niveau et ne fait pas partie du bloc authentication. Définissez-la, ou définissez POTATO_SECRET_KEY. Si aucune des deux n'est présente, Potato génère une clé aléatoire, ce qui signifie que chaque redémarrage invalide toutes les sessions, et il écrit un avertissement dans le journal lorsque OAuth est configuré sans elle.

Potato expose deux routes OAuth. /auth/login/<provider> lance le flux et /auth/callback/<provider> reçoit la réponse. Le callback est l'URL que vous enregistrez auprès du fournisseur, donc pour Google il s'agit de https://your-domain.com/auth/callback/google. Il n'existe pas de clé de configuration redirect_uri ; la route est dérivée du nom du fournisseur.

Google

Créez des identifiants OAuth 2.0 de type "Web application" dans la Google Cloud Console, puis ajoutez https://your-domain.com/auth/callback/google comme URI de redirection autorisée. Pour le développement local, http://localhost:8000/auth/callback/google est accepté.

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

allowed_domain prend une seule chaîne de domaine. Seuls les comptes dont l'adresse e-mail appartient à ce domaine peuvent se connecter.

GitHub

Enregistrez une OAuth app dans GitHub Settings > Developer settings > OAuth Apps, avec l'URL de callback définie sur 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 prend un seul nom d'organisation. Lorsqu'il est défini, Potato demande automatiquement le scope read:org, vous n'avez donc pas besoin de lister les scopes vous-même. L'annotateur doit être membre public de l'organisation pour que la vérification voie son appartenance.

HuggingFace

Créez une application OAuth sur huggingface.co/settings/applications.

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

Le fournisseur HuggingFace se connecte à l'endpoint de découverte OIDC de HuggingFace et demande openid profile email par défaut.

OIDC générique

Tout nom de fournisseur autre que google, github et huggingface est traité comme de l'OIDC générique et requiert discovery_url. Donnez-lui un display_name pour que le bouton de connexion soit lisible.

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 de callback à enregistrer auprès du fournisseur suit la clé du fournisseur, donc un bloc nommé oidc utilise https://your-domain.com/auth/callback/oidc.

URL de découverte des fournisseurs courants :

FournisseurURL de découverte
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

scopes vaut par défaut ["openid", "email", "profile"].

Plusieurs fournisseurs

providers accepte autant d'entrées que nécessaire, et chacune affiche son propre bouton de connexion.

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}

Avec user_identity_field: "email", une personne qui se connecte via Google puis, plus tard, via le fournisseur de l'université avec la même adresse est le même utilisateur Potato et retrouve les mêmes attributions.

Identité et inscription automatique

user_identity_field détermine quelle claim du fournisseur devient le nom d'utilisateur Potato. Il accepte email, username, sub ou name, et vaut email par défaut. La valeur compte, car les répertoires de sortie des annotateurs portent le nom de l'utilisateur ; la changer en cours d'étude sépare donc un annotateur de son travail antérieur.

auto_register détermine si une première connexion réussie crée le compte Potato. Il vaut true par défaut.

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

Le contrôle d'accès passe par le fournisseur et non par une liste d'adresses dans la configuration. Pour limiter qui peut se connecter, utilisez allowed_domain pour Google, allowed_org pour GitHub, ou les contrôles de groupes et d'attribution d'applications de votre fournisseur OIDC.

Mode mixte

allow_local_login ajoute le formulaire nom d'utilisateur et mot de passe sous les boutons SSO. Il vaut false par défaut, donc le SSO remplace la connexion locale à moins que vous ne l'activiez.

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

Une migration fait fonctionner les deux pendant un temps. Faites passer les annotateurs au fournisseur, puis retirez allow_local_login une fois que tout le monde s'est connecté par ce biais. Le mode mixte couvre aussi les études qui associent des annotateurs de l'institution à des annotateurs externes sans compte dans votre institution.

Les comptes locaux en mode mixte utilisent le même hachage PBKDF2 salé que tout autre compte à mot de passe. Voir Gestion des mots de passe pour les procédures de réinitialisation.

Sessions

La durée de vie des sessions est une clé de premier niveau, exprimée en jours :

yaml
session_lifetime_days: 7

C'est une secret_key stable qui permet aux sessions de survivre à un redémarrage.

Accès administrateur

Les endpoints d'administration s'authentifient avec une clé d'API plutôt que par le bloc OAuth :

yaml
admin_api_key: ${POTATO_ADMIN_API_KEY}

Potato lit admin_api_key d'abord dans la configuration, puis dans la variable d'environnement POTATO_ADMIN_API_KEY. Si aucune n'est définie, il génère une clé et l'écrit dans admin_api_key.txt dans le répertoire de la tâche. Les appelants la présentent dans l'en-tête X-API-Key.

HTTPS

Les fournisseurs OAuth exigent des URI de redirection HTTPS en production. localhost en est exempté, donc le développement en HTTP fonctionne sans certificat.

Derrière un reverse proxy, transmettez le schéma d'origine, sinon l'URL de callback construite par Potato utilisera http et le fournisseur la rejettera :

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;
}

Référence de configuration

CléTypeDéfautDescription
authentication.methodstringin_memoryin_memory, database, clerk ou oauth
authentication.providersdictrequis pour oauthUne entrée par fournisseur, indexée par le nom du fournisseur
authentication.user_identity_fieldstringemailemail, username, sub ou name
authentication.allow_local_loginboolfalseAffiche le formulaire de mot de passe à côté des boutons SSO
authentication.auto_registerbooltrueCrée le compte Potato à la première connexion
secret_keystringaléatoirePremier niveau. Une valeur stable est nécessaire pour des sessions persistantes
session_lifetime_daysint7Durée des sessions, au premier niveau
admin_api_keystringgénéréeClé de premier niveau pour les endpoints d'administration

Clés par fournisseur :

CléTypeRequiseDescription
client_idstringouiFournie par le fournisseur
client_secretstringouiFournie par le fournisseur
discovery_urlstringpour l'OIDC génériqueEndpoint de découverte OIDC
display_namestringnonTexte du bouton de connexion
allowed_domainstringnonRestreint à un domaine de messagerie
allowed_orgstringnonRestreint à une organisation GitHub
scopeslistnonVaut par défaut ["openid", "email", "profile"]

Exemple complet

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"

Dépannage

Le fournisseur rejette l'URI de redirection. L'URI enregistrée auprès du fournisseur doit correspondre exactement à /auth/callback/<provider>. Le schéma et tout chemin final font partie de la correspondance, donc un proxy qui ne transmet pas X-Forwarded-Proto produit un callback http que le fournisseur rejette.

Tout le monde est déconnecté après un redémarrage. secret_key n'est pas définie, donc Potato en a généré une nouvelle au démarrage.

Une restriction d'organisation GitHub rejette un membre. Son appartenance à l'organisation est privée. Soit il la rend publique, soit vous retirez allowed_org et contrôlez l'accès autrement.

La validation échoue avec "providers is required". method: "oauth" nécessite au moins une entrée sous providers. La valeur de la méthode est oauth, pas le nom d'un fournisseur.


Pour aller plus loin

Pour les détails d'implémentation, consultez la documentation source.