본문으로 건너뛰기

Tahoe로 로그인

리크루터가 Tahoe 계정으로 내 앱에 로그인하고 자신의 데이터를 읽거나 바꾸게 하세요.

Sign in with Tahoe(Tahoe로 로그인)를 사용하면 리크루터가 이미 가진 Tahoe 계정으로 내 앱에 로그인할 수 있습니다. 그러면 앱이 리크루터를 대신해 Tahoe API를 호출할 수 있으며, 리크루터가 Tahoe에서 볼 수 있고 공유에 동의한 정보만 읽습니다.

Tahoe는 PKCE를 사용하는 authorization code 흐름의 표준 OpenID Connect(OIDC) 제공자입니다. 사용 중인 스택에 OIDC 클라이언트 라이브러리가 있다면, 아래의 디스커버리 문서를 가리키도록 설정하는 것만으로 대부분의 작업이 끝납니다.

앱 등록하기

앱을 얻는 방법은 두 가지이며, 아래의 로그인 흐름은 둘 다 같습니다.

  • 내 워크스페이스용 앱. 소유자나 관리자가 설정의 개발자 탭에서 등록합니다. 클라이언트 시크릿을 서버에 보관하는 기밀(confidential) 앱이며, https 리디렉션 URI만 받고, 등록한 워크스페이스에서만 승인할 수 있습니다. 셀프 서비스 등록은 단계적으로 열고 있습니다. 내 워크스페이스에서 쓸 수 있게 되면 설정에 해당 섹션이 나타납니다. 앱 연결하기에서 과정을 안내합니다.
  • 여러 고객을 위한 앱, 또는 시크릿을 보관할 수 없는 앱. Tahoe가 대신 등록합니다. [email protected]으로 다음 내용을 보내 주세요.
  • 동의 화면에서 리크루터에게 보일 앱 이름
  • 사용할 모든 리디렉션 URI를 정확하게, 로컬 개발용 URI까지 포함해서(예: https://app.example.com/auth/tahoe/callback)
  • 로그아웃 후 사용자를 돌려보낼 주소(있는 경우)
  • 필요한 스코프: openid, profile, email, offline_access, 그리고 스코프 표에 있는 API 스코프
  • 앱이 시크릿을 안전하게 보관할 수 있는지(서버 측 앱), 없는지(모바일 앱이나 단일 페이지 앱)

어느 쪽이든 thc_로 시작하는 client_id를 받고, 서버 측 앱이라면 클라이언트 시크릿도 받습니다.

디스커버리

디스커버리 문서에 아래의 모든 엔드포인트가 나와 있습니다. 엔드포인트를 직접 입력하지 말고 이 문서로 클라이언트를 설정하세요.

요청
curl -s https://tahoe.workonward.com/api/partner/v1/.well-known/openid-configuration
응답(일부 생략)
{
  "issuer": "https://tahoe.workonward.com/api/partner/v1",
  "authorization_endpoint": "https://tahoe.workonward.com/api/partner/v1/oauth/authorize",
  "token_endpoint": "https://tahoe.workonward.com/api/partner/v1/oauth/token",
  "userinfo_endpoint": "https://tahoe.workonward.com/api/partner/v1/oauth/userinfo",
  "jwks_uri": "https://tahoe.workonward.com/api/partner/v1/.well-known/jwks.json",
  "revocation_endpoint": "https://tahoe.workonward.com/api/partner/v1/oauth/revoke",
  "end_session_endpoint": "https://tahoe.workonward.com/api/partner/v1/oauth/logout",
  "response_types_supported": ["code"],
  "response_modes_supported": ["query"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "subject_types_supported": ["pairwise"],
  "id_token_signing_alg_values_supported": ["RS256"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": [
    "client_secret_post", "client_secret_basic", "none"
  ]
}

로그인 흐름

사용자를 Tahoe로 보내기

로그인할 때마다 새 PKCE code_verifier를 만들어 사용자 세션에 보관하세요. 그 SHA-256 해시를 code_challenge로 보냅니다.

PKCE 쌍 만들기
import base64
import hashlib
import secrets

code_verifier = secrets.token_urlsafe(64)
code_challenge = (
    base64.urlsafe_b64encode(hashlib.sha256(code_verifier.encode()).digest())
    .rstrip(b"=")
    .decode()
)

그런 다음 브라우저를 인가 엔드포인트로 리디렉션하세요.

인가 요청
https://tahoe.workonward.com/api/partner/v1/oauth/authorize
  ?client_id=thc_7f3a9c21d84e4b6fa0c5e19b2d7a6e31
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Fauth%2Ftahoe%2Fcallback
  &response_type=code
  &scope=openid%20profile%20email%20offline_access%20jobs%3Aread
  &state=<random, tied to the user's session>
  &nonce=<random, saved for the id_token check>
  &code_challenge=<base64url(sha256(code_verifier))>
  &code_challenge_method=S256
파라미터필수참고
client_id예Tahoe가 발급한 ID.
redirect_uri예등록한 URI와 정확히 일치해야 합니다.
response_type예code만 쓸 수 있습니다.
scope예openid가 반드시 있어야 합니다. 필요한 profile, email, offline_access, API 스코프를 공백으로 구분해 추가하세요.
state사실상 필수사이트 간 요청 위조를 막습니다. 세션에 연결해 두고 돌아왔을 때 확인하세요.
nonce사실상 필수저장해 두고, id_token 안에 그대로 돌아오는지 확인하세요.
code_challenge예code_verifier의 base64url SHA-256 해시.
code_challenge_method예S256. plain은 거부됩니다.

사용자가 승인하기

Tahoe는 “[Your app] wants to access your Tahoe account”라는 제목의 동의 화면을 보여 줍니다. 로그인한 계정, 사용자가 돌아갈 주소, 앱이 읽을 수 있게 되는 항목이 표시됩니다. 연락처나 이력서를 요청했다면 앱이 후보자의 개인 데이터를 요청한다는 경고가 나옵니다. offline_access를 요청했다면 앱이 계속 연결된 상태로 남는다는 안내가 나옵니다. 사용자는 Allow [your app](허용) 또는 Cancel(취소)을 선택합니다.

고객이 등록한 앱은 Tahoe가 검토하지 않았으므로 미확인으로 표시되며, 등록한 워크스페이스의 이름도 함께 나옵니다. 사용자가 부여할 수 없는 스코프는 체크되지 않은 채 이유와 함께 표시됩니다. 누가 무엇을 승인할 수 있는가를 참고하세요.

승인하면 브라우저가 코드와 함께 리디렉션 URI로 돌아옵니다.

콜백
GET https://app.example.com/auth/tahoe/callback?code=<code>&state=<your state>

코드로 무엇을 하든 그 전에 state를 세션 값과 비교하세요. 코드는 한 번만 쓸 수 있고 5분 뒤에 만료됩니다.

코드를 토큰으로 교환하기

토큰 요청
curl -s -X POST https://tahoe.workonward.com/api/partner/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=authorization_code \
  -d code=<code> \
  -d redirect_uri=https://app.example.com/auth/tahoe/callback \
  -d client_id=thc_7f3a9c21d84e4b6fa0c5e19b2d7a6e31 \
  -d client_secret=<your client secret> \
  -d code_verifier=<the code_verifier you made>
응답
{
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "openid profile email offline_access jobs:read",
  "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjNnY3M1ZGdoM01zcVJDeW5KZWNlOHNYaCJ9...",
  "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjNnY3M1ZGdoM01zcVJDeW5KZWNlOHNYaCJ9...",
  "refresh_token": "..."
}

서버 측 앱은 client_secret_post(위 예시) 또는 client_secret_basic으로 인증합니다. 시크릿이 없는 앱은 client_id만 보내며, 시크릿을 보내서는 안 됩니다.

토큰유효 기간용도
access_token1시간API 키처럼 Tahoe API 호출에 씁니다.
id_token1시간사용자에 대한 클레임이 담긴 서명된(RS256) JWT. 검증해서 쓰고, API로 보내지 마세요.
refresh_token사용하지 않으면 30일, 최대 90일offline_access를 요청한 경우에만 받습니다. 갱신할 때마다 새 토큰이 함께 옵니다.

id_token 검증하기

표준 검사를 모두 하세요. jwks_uri로 서명을 확인하고, iss가 issuer와 같은지, aud가 내 client_id와 같은지, exp가 미래 시각인지, nonce가 보낸 값과 같은지 확인합니다.

sub 클레임은 앱마다 다릅니다

subject 식별자는 pairwise 방식입니다. 같은 Tahoe 사용자라도 내 앱에서 받는 sub 값은 다른 앱에서 받는 값과 다릅니다. 두 앱이 식별자를 비교해 같은 사용자인지 맞춰 볼 수는 없습니다.

내 앱에서 sub 값은 자체 사용자 레코드의 기본 키로 쓰기 좋으며, 내 앱 안에서는 바뀌지 않습니다. 다른 곳에서는 의미가 없습니다. Tahoe 전체에서 통하는 사용자 ID처럼 로그에 남기지 말고, Tahoe 지원팀이 이 값을 알아볼 것이라 기대하지 마세요.

사용자로서 API 호출하기

액세스 토큰은 API 키와 똑같이, 같은 기본 URL로 보냅니다.

요청
curl -s https://tahoe.workonward.com/api/partner/v1/jobs \
  -H "Authorization: Bearer <access_token>"
  • 읽을 수 있는 범위는 앱이 요청한 API 스코프와 사용자가 Tahoe에서 볼 수 있는 것이 겹치는 부분입니다. 토큰은 그 뒤에 있는 사람이 볼 수 있는 범위를 넘어서지 않습니다.
  • 워크스페이스는 사용자가 앱을 승인할 때 로그인해 있던 워크스페이스입니다. 토큰은 그 워크스페이스만 읽으므로 따로 지정할 필요가 없습니다. 사용자가 그 워크스페이스에서 제외되면 다음 갱신이 실패합니다. 토큰에 쓰기 스코프나 민감한 스코프가 있으면, 사용자가 여전히 소유자나 관리자인 워크스페이스에서만 동작합니다. 강등된 사용자는 앱의 읽기 접근은 유지하고, 나머지는 다음 요청부터 잃습니다.
  • 쓰기는 켜져 있어야 합니다. 연결된 앱의 쓰기가 켜지기 전에는 이 흐름으로 받은 토큰의 모든 쓰기가 403 write_requires_api_key를 반환합니다. 켜지면 쓰기는 앱이 아니라 로그인한 사람이 한 것으로 기록됩니다. 앱 연결하기를 참고하세요.
  • 사용자 이름으로 기록됩니다. 토큰으로 개인 데이터를 읽으면 앱뿐 아니라 해당 사용자 이름으로도 기록됩니다.

토큰 갱신하기

갱신
curl -s -X POST https://tahoe.workonward.com/api/partner/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=refresh_token \
  -d refresh_token=<the current refresh token> \
  -d client_id=thc_7f3a9c21d84e4b6fa0c5e19b2d7a6e31 \
  -d client_secret=<your client secret>

갱신할 때마다 새 리프레시 토큰이 반환되고, 보낸 토큰은 더 이상 쓸 수 없게 됩니다. 새 토큰을 사용하기 전에 먼저 저장하세요. scope 파라미터를 보내 원래 승인보다 적은 스코프를 요청할 수는 있지만, 더 많이 요청할 수는 없습니다.

리프레시 토큰은 30일 동안 사용하지 않으면 만료됩니다. 아무리 자주 사용하더라도, 한 번의 로그인에서 이어지는 리프레시 토큰은 그 로그인으로부터 90일 뒤에 끝나며 사용자는 다시 로그인합니다.

이미 교환한 리프레시 토큰은 다시 교환할 수 없습니다. 갱신 응답을 타임아웃 등으로 받지 못하면 새 토큰도 함께 사라집니다. Tahoe가 짧은 유예 시간을 켜 둔 경우에는, 첫 사용 직후의 재시도에 세션을 끝내지 않고 같은 새 토큰을 돌려줍니다. Tahoe가 따로 알리지 않는 한 꺼져 있으므로 없다고 생각하고 구현하세요.

접근이 끝나는 경우

다음 경우에 토큰이 작동을 멈추고, 다음 갱신이 실패합니다.

  • 사용자가 Tahoe의 Settings → Connected apps에 있는 Apps you signed in to with Tahoe 섹션에서 앱 연결을 해제한 경우
  • 사용자가 Tahoe 비밀번호를 재설정한 경우
  • 사용자가 로그인한 워크스페이스에서 제외되었거나, 계정이 해지된 경우
  • 위에서 설명한 대로 리프레시 토큰이 재사용된 경우

갱신 실패(invalid_grant)는 재시도할 일이 아니라 “이 사용자를 다시 로그인시키라”는 뜻으로 처리하세요.

엔드포인트 레퍼런스

GET/.well-known/openid-configuration공개

디스커버리 문서입니다. 공개되어 있으며 5분 동안 캐시할 수 있습니다.

GET/.well-known/jwks.json공개

공개 서명 키로, 역시 5분 동안 캐시할 수 있습니다. 키 교체 중에는 항목이 두 개 나타나므로 kid로 맞추세요.

GET/oauth/authorize브라우저 리디렉션

로그인을 시작하고 브라우저를 Tahoe 동의 화면으로 보냅니다.

POST/oauth/token클라이언트 인증

코드를 토큰으로 교환하거나 토큰을 갱신합니다. client_secret_post, client_secret_basic, 그리고 시크릿이 없는 앱을 위한 none을 받습니다. authorization_code와 refresh_token 그랜트만 있습니다.

GET/oauth/userinfo액세스 토큰

액세스 토큰의 주인인 사용자에 대한 클레임입니다. 각 클레임은 부여된 스코프에 따라 달라집니다. email 스코프가 없으면 응답에 이메일이 없으며, 이는 오류가 아닙니다. 값이 없는 클레임은 빠집니다. sub는 항상 있습니다. 사용자가 앱 연결을 해제했다면, 액세스 토큰이 아직 만료되지 않았더라도 401을 반환합니다.

요청
curl -s https://tahoe.workonward.com/api/partner/v1/oauth/userinfo \
  -H "Authorization: Bearer <access_token>"
응답
{
  "sub": "9f4c1e7a2b...",
  "name": "Jordan Rivera",
  "given_name": "Jordan",
  "family_name": "Rivera",
  "updated_at": 1789245645,
  "email": "[email protected]",
  "email_verified": true
}

POST/oauth/revoke클라이언트 인증

리프레시 토큰을 폐기합니다. 사용자가 앱에서 로그아웃하거나 Tahoe 연결을 해제할 때 호출해서, 승인이 남아 있지 않게 하세요. 토큰이 존재했는지와 상관없이 200으로 응답합니다.

요청
curl -s -X POST https://tahoe.workonward.com/api/partner/v1/oauth/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d token=<refresh token> \
  -d client_id=thc_7f3a9c21d84e4b6fa0c5e19b2d7a6e31 \
  -d client_secret=<your client secret>

GET/oauth/logout브라우저 리디렉션

해당 브라우저에서 사용자를 Tahoe에서 로그아웃시킵니다. 로그아웃 후 사용자를 앱으로 돌려보내려면 client_id와 등록된 post_logout_redirect_uri(정확히 일치해야 함)를 보내고, 필요하면 state도 함께 보내세요.

로그아웃 요청
https://tahoe.workonward.com/api/partner/v1/oauth/logout
  ?client_id=thc_7f3a9c21d84e4b6fa0c5e19b2d7a6e31
  &post_logout_redirect_uri=https%3A%2F%2Fapp.example.com%2Fsigned-out
  &state=<optional>

체크리스트

  • 디스커버리 문서로 설정하고, 엔드포인트를 직접 입력하지 않습니다.
  • S256을 쓰는 PKCE, 그리고 로그인할 때마다 새 code_verifier.
  • 세션에 연결하고 돌아왔을 때 확인하는 state.
  • 저장해 두고 id_token 안에서 확인하는 nonce.
  • JWKS는 캐시하고, 키는 kid로 고르며, 모르는 kid를 만나면 다시 가져옵니다.
  • 사용자마다 한 번에 하나씩 갱신하고, 새 토큰은 쓰기 전에 저장합니다.
  • 로그아웃할 때 리프레시 토큰을 폐기합니다.
  • 갱신이 실패하면 재시도하지 않고 사용자를 다시 로그인시킵니다.

관련 문서