Skip to content

SSO 및 OAuth 인증

Potato에서 Google, GitHub, HuggingFace, 일반 OIDC 로그인을 설정합니다. 이메일 도메인이나 GitHub 조직으로 접근을 제한하고, 로컬 비밀번호 로그인과 SSO를 함께 운영할 수 있습니다.

v2.3.0의 새로운 기능

기본적으로 Potato는 어노테이터가 아무 사용자 이름이나 입력해 로그인하는 간단한 사용자 이름 기반 로그인을 사용합니다. 로컬 개발과 소규모 프로젝트에는 이것으로 충분하지만, 운영 환경 배포에서는 무단 접근을 막고 어노테이터의 신원을 확인하며 기관 요구 사항을 충족하기 위해 제대로 된 인증이 필요합니다.

SSO는 한곳에서 설정합니다. authentication.method를 oauth로 설정한 다음 authentication.providers 아래에 공급자를 하나 이상 나열합니다. 각 공급자는 로그인 페이지에 자체 버튼을 갖습니다.

Potato에는 google, github, huggingface 세 공급자의 엔드포인트 메타데이터가 내장되어 있습니다. 그 밖의 공급자 이름은 일반 OIDC로 처리되며 discovery_url이 필요합니다.

빠른 시작

최소한의 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}

비밀 값은 파일이 아니라 환경 변수에 설정합니다.

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는 최상위 키이며 authentication 블록에 속하지 않습니다. 이 키를 설정하거나 POTATO_SECRET_KEY를 설정하십시오. 둘 다 없으면 Potato가 임의의 키를 생성하므로 재시작할 때마다 모든 세션이 무효화됩니다. 또한 이 키 없이 OAuth를 설정하면 Potato는 경고를 기록합니다.

Potato는 두 개의 OAuth 경로를 제공합니다. /auth/login/<provider>는 인증 흐름을 시작하고 /auth/callback/<provider>는 응답을 받습니다. 공급자에 등록하는 URL은 콜백 경로이므로 Google의 경우 https://your-domain.com/auth/callback/google입니다. redirect_uri 설정 키는 없으며, 경로는 공급자 이름에서 정해집니다.

Google

Google Cloud Console에서 "웹 애플리케이션" 유형의 OAuth 2.0 자격 증명을 만들고, https://your-domain.com/auth/callback/google을 승인된 리디렉션 URI로 추가합니다. 로컬 개발에서는 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은 도메인 문자열 하나를 받습니다. 이메일이 해당 도메인에 속한 계정만 로그인할 수 있습니다.

GitHub

GitHub Settings > Developer settings > OAuth Apps에서 OAuth 앱을 등록하고, 콜백 URL을 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는 조직 이름 하나를 받습니다. 이 키가 설정되면 Potato가 read:org 범위를 자동으로 요청하므로 범위를 직접 나열할 필요가 없습니다. 이 검사에서 소속이 확인되려면 어노테이터가 해당 조직의 구성원 자격을 공개로 설정해 두어야 합니다.

HuggingFace

huggingface.co/settings/applications에서 OAuth 애플리케이션을 만듭니다.

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

HuggingFace 공급자는 HuggingFace의 OIDC 디스커버리 엔드포인트에 연결하며, 기본적으로 openid profile email을 요청합니다.

일반 OIDC

google, github, huggingface 이외의 공급자 이름은 모두 일반 OIDC로 처리되며 discovery_url이 필요합니다. 로그인 버튼을 알아보기 쉽도록 display_name을 지정하십시오.

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"

공급자에 등록할 콜백 URL은 공급자 키를 따르므로, oidc라는 이름의 블록은 https://your-domain.com/auth/callback/oidc를 사용합니다.

주요 공급자의 디스커버리 URL:

공급자디스커버리 URL
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의 기본값은 ["openid", "email", "profile"]입니다.

여러 공급자

providers에는 필요한 만큼 항목을 넣을 수 있으며, 각 항목은 자체 로그인 버튼을 표시합니다.

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}

user_identity_field: "email"을 사용하면 Google로 로그인한 사람이 나중에 같은 주소로 대학 공급자를 통해 로그인해도 같은 Potato 사용자로 처리되며, 같은 할당 항목으로 돌아갑니다.

신원과 자동 등록

user_identity_field는 공급자가 보내는 클레임 중 어느 것을 Potato 사용자 이름으로 쓸지 정합니다. email, username, sub, name 중 하나를 받으며 기본값은 email입니다. 어노테이터 출력 디렉터리의 이름이 사용자 이름을 따르기 때문에 이 값이 중요합니다. 연구 도중에 바꾸면 어노테이터가 이전 작업과 분리됩니다.

auto_register는 첫 로그인에 성공했을 때 Potato 계정을 만들지 여부를 제어합니다. 기본값은 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

접근 제어는 설정 파일의 주소 목록이 아니라 공급자를 통해 이루어집니다. 로그인할 수 있는 사람을 제한하려면 Google에는 allowed_domain을, GitHub에는 allowed_org를 사용하고, OIDC 공급자에서는 그룹 및 앱 할당 제어 기능을 사용하십시오.

혼합 모드

allow_local_login은 SSO 버튼 아래에 사용자 이름과 비밀번호 입력 양식을 추가합니다. 기본값은 false이므로, 이 키를 켜지 않으면 SSO가 로컬 로그인을 대체합니다.

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

마이그레이션 중에는 한동안 두 방식을 함께 운영합니다. 어노테이터를 옮긴 뒤, 모두가 공급자를 통해 로그인하고 나면 allow_local_login을 제거합니다. 혼합 모드는 기관 소속 어노테이터와 해당 기관 계정이 없는 외부 어노테이터를 함께 쓰는 연구에도 적합합니다.

혼합 모드의 로컬 계정은 다른 비밀번호 계정과 동일한 솔트 적용 PBKDF2 해싱을 사용합니다. 재설정 절차는 비밀번호 관리를 참고하십시오.

세션

세션 수명은 일 단위의 최상위 키입니다.

yaml
session_lifetime_days: 7

재시작 후에도 세션이 유지되려면 고정된 secret_key가 있어야 합니다.

관리자 접근

관리자 엔드포인트는 OAuth 블록이 아니라 API 키로 인증합니다.

yaml
admin_api_key: ${POTATO_ADMIN_API_KEY}

Potato는 먼저 설정 파일에서 admin_api_key를 읽고, 그다음 POTATO_ADMIN_API_KEY 환경 변수를 읽습니다. 둘 다 설정되어 있지 않으면 키를 생성해 작업 디렉터리의 admin_api_key.txt에 기록합니다. 호출하는 쪽은 이 키를 X-API-Key 헤더로 제시합니다.

HTTPS

OAuth 공급자는 운영 환경에서 HTTPS 리디렉션 URI를 요구합니다. localhost는 예외이므로 개발 중에는 인증서 없이 HTTP로 작업할 수 있습니다.

리버스 프록시 뒤에서는 원래 스킴을 전달해야 합니다. 그렇지 않으면 Potato가 만드는 콜백 URL이 http를 사용하게 되고 공급자가 이를 거부합니다.

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

설정 레퍼런스

키유형기본값설명
authentication.methodstringin_memoryin_memory, database, clerk 또는 oauth
authentication.providersdictoauth에 필수공급자당 하나의 항목, 공급자 이름을 키로 사용
authentication.user_identity_fieldstringemailemail, username, sub 또는 name
authentication.allow_local_loginboolfalseSSO 버튼과 함께 비밀번호 양식 표시
authentication.auto_registerbooltrue첫 로그인 시 Potato 계정 생성
secret_keystring임의 값최상위. 세션을 유지하려면 고정된 값이 필요
session_lifetime_daysint7최상위 세션 지속 기간
admin_api_keystring자동 생성관리자 엔드포인트용 최상위 키

공급자별 키:

키유형필수 여부설명
client_idstring예공급자에서 발급
client_secretstring예공급자에서 발급
discovery_urlstring일반 OIDC에 필수OIDC 디스커버리 엔드포인트
display_namestring아니요로그인 버튼 텍스트
allowed_domainstring아니요이메일 도메인 하나로 제한
allowed_orgstring아니요GitHub 조직 하나로 제한
scopeslist아니요기본값은 ["openid", "email", "profile"]

전체 예시

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"

문제 해결

공급자가 리디렉션 URI를 거부합니다. 공급자에 등록한 URI는 /auth/callback/<provider>와 정확히 일치해야 합니다. 스킴과 뒤에 붙는 경로도 일치 여부에 포함되므로, X-Forwarded-Proto를 전달하지 않는 프록시는 http 콜백을 만들어 내고 공급자는 이를 거부합니다.

재시작 후 모든 사용자가 로그아웃됩니다. secret_key가 설정되지 않아 Potato가 부팅할 때 새 키를 생성한 것입니다.

GitHub 조직 제한이 구성원을 거부합니다. 해당 구성원의 조직 소속이 비공개로 되어 있습니다. 본인이 공개로 바꾸거나, allowed_org를 제거하고 다른 방법으로 접근을 제어하십시오.

"providers is required" 오류로 검증에 실패합니다. method: "oauth"에는 providers 아래에 항목이 하나 이상 있어야 합니다. method 값은 공급자 이름이 아니라 oauth입니다.


더 읽어보기

구현 세부 정보는 원본 문서를 참고하십시오.