Skip to content

Autenticação SSO e OAuth

Configure o login com Google, GitHub, HuggingFace e OIDC genérico no Potato. Restrinja o acesso por domínio de e-mail ou organização do GitHub e use SSO junto com o login local por senha.

Novo na v2.3.0

Por padrão, o Potato usa um login simples baseado em nome de usuário, no qual os anotadores digitam qualquer nome de usuário para entrar. Isso funciona para desenvolvimento local e projetos pequenos, mas implantações em produção precisam de autenticação adequada para impedir acesso não autorizado, confirmar a identidade dos anotadores e atender a requisitos institucionais.

O SSO é configurado em um só lugar. Defina authentication.method como oauth e liste um ou mais provedores em authentication.providers. Cada provedor ganha seu próprio botão na página de login.

O Potato tem metadados de endpoint embutidos para três provedores: google, github e huggingface. Qualquer outro nome de provedor é tratado como OIDC genérico e precisa de uma discovery_url.

Início rápido

Uma configuração mínima do 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}

Defina os segredos no ambiente, e não no arquivo:

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 é uma chave de nível superior e não faz parte do bloco authentication. Defina-a ou defina POTATO_SECRET_KEY. Quando nenhuma das duas está presente, o Potato gera uma chave aleatória, o que significa que cada reinicialização invalida todas as sessões, e registra um aviso no log quando o OAuth está configurado sem ela.

O Potato atende duas rotas OAuth. /auth/login/<provider> inicia o fluxo e /auth/callback/<provider> recebe a resposta. O callback é a URL que você registra no provedor, então para o Google ela é https://your-domain.com/auth/callback/google. Não existe uma chave de configuração redirect_uri; a rota é derivada do nome do provedor.

Google

Crie credenciais OAuth 2.0 do tipo "Web application" no Google Cloud Console e adicione https://your-domain.com/auth/callback/google como URI de redirecionamento autorizado. Para desenvolvimento local, http://localhost:8000/auth/callback/google é aceito.

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

allowed_domain recebe uma única string de domínio. Somente contas cujo e-mail pertence a esse domínio podem fazer login.

GitHub

Registre um OAuth app em GitHub Settings > Developer settings > OAuth Apps, com a URL de callback definida como 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 recebe um único nome de organização. Quando ele está definido, o Potato solicita o escopo read:org automaticamente, então você não precisa listar os escopos. O anotador precisa ter associação pública à organização para que a verificação a enxergue.

HuggingFace

Crie um aplicativo OAuth em huggingface.co/settings/applications.

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

O provedor HuggingFace se conecta ao endpoint de descoberta OIDC do HuggingFace e solicita openid profile email por padrão.

OIDC genérico

Qualquer nome de provedor diferente de google, github e huggingface é tratado como OIDC genérico e exige discovery_url. Dê a ele um display_name para que o botão de login fique legível.

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"

A URL de callback a registrar no provedor segue a chave do provedor, então um bloco chamado oidc usa https://your-domain.com/auth/callback/oidc.

URLs de descoberta de provedores comuns:

ProvedorURL de descoberta
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

O padrão de scopes é ["openid", "email", "profile"].

Vários provedores

providers aceita quantas entradas você precisar, e cada uma exibe seu próprio botão de login.

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}

Com user_identity_field: "email", alguém que entra pelo Google e depois pelo provedor da universidade com o mesmo endereço é o mesmo usuário do Potato e volta para as mesmas atribuições.

Identidade e registro automático

user_identity_field define qual claim do provedor se torna o nome de usuário no Potato. Aceita email, username, sub ou name, e o padrão é email. O valor importa porque os diretórios de saída dos anotadores recebem o nome do usuário, então alterá-lo no meio de um estudo separa o anotador do trabalho que ele já fez.

auto_register controla se o primeiro login bem-sucedido cria a conta no Potato. O padrão é 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

O controle de acesso passa pelo provedor, e não por uma lista de endereços na configuração. Para limitar quem pode fazer login, use allowed_domain no Google, allowed_org no GitHub ou os controles de grupos e de atribuição de aplicativos do seu provedor OIDC.

Modo misto

allow_local_login adiciona o formulário de usuário e senha abaixo dos botões de SSO. O padrão é false, então o SSO substitui o login local a menos que você o ative.

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

Uma migração mantém os dois por um tempo. Transfira os anotadores e depois remova allow_local_login quando todos tiverem entrado pelo provedor. O modo misto também atende estudos que combinam anotadores da instituição com anotadores externos que não têm conta na sua instituição.

Contas locais no modo misto usam o mesmo hash PBKDF2 com salt de qualquer outra conta com senha. Veja Gerenciamento de senhas para os fluxos de redefinição.

Sessões

A duração da sessão é uma chave de nível superior, em dias:

yaml
session_lifetime_days: 7

É uma secret_key estável que faz as sessões sobreviverem a uma reinicialização.

Acesso de administrador

Os endpoints de administração se autenticam com uma chave de API, e não pelo bloco OAuth:

yaml
admin_api_key: ${POTATO_ADMIN_API_KEY}

O Potato lê admin_api_key primeiro da configuração e depois da variável de ambiente POTATO_ADMIN_API_KEY. Se nenhuma estiver definida, ele gera uma chave e a grava em admin_api_key.txt no diretório da tarefa. Quem faz a chamada a envia no cabeçalho X-API-Key.

HTTPS

Provedores OAuth exigem URIs de redirecionamento HTTPS em produção. localhost é isento, então o desenvolvimento via HTTP funciona sem certificado.

Atrás de um proxy reverso, repasse o esquema original, ou a URL de callback que o Potato monta usará http e o provedor a rejeitará:

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

Referência de configuração

ChaveTipoPadrãoDescrição
authentication.methodstringin_memoryin_memory, database, clerk ou oauth
authentication.providersdictobrigatório para oauthUma entrada por provedor, indexada pelo nome do provedor
authentication.user_identity_fieldstringemailemail, username, sub ou name
authentication.allow_local_loginboolfalseMostra o formulário de senha junto aos botões de SSO
authentication.auto_registerbooltrueCria a conta no Potato no primeiro login
secret_keystringaleatórioNível superior. Um valor estável é necessário para sessões persistentes
session_lifetime_daysint7Duração da sessão, no nível superior
admin_api_keystringgeradaChave de nível superior para os endpoints de administração

Chaves por provedor:

ChaveTipoObrigatóriaDescrição
client_idstringsimFornecido pelo provedor
client_secretstringsimFornecido pelo provedor
discovery_urlstringpara OIDC genéricoEndpoint de descoberta OIDC
display_namestringnãoTexto do botão de login
allowed_domainstringnãoRestringe a um domínio de e-mail
allowed_orgstringnãoRestringe a uma organização do GitHub
scopeslistnãoPadrão ["openid", "email", "profile"]

Exemplo 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"

Solução de problemas

O provedor rejeita o URI de redirecionamento. O URI registrado no provedor precisa corresponder exatamente a /auth/callback/<provider>. O esquema e qualquer caminho final contam para a correspondência, então um proxy que não repassa X-Forwarded-Proto produz um callback http que o provedor rejeita.

Todos são desconectados depois de uma reinicialização. secret_key não está definida, então o Potato gerou uma nova na inicialização.

Uma restrição por organização do GitHub rejeita um membro. A associação dele à organização é privada. Ou ele a torna pública, ou você remove allowed_org e controla o acesso de outra forma.

A validação falha com "providers is required". method: "oauth" precisa de pelo menos uma entrada em providers. O valor do método é oauth, não o nome de um provedor.


Leitura adicional

Para detalhes de implementação, consulte a documentação fonte.