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로 보냅니다.
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_token | 1시간 | API 키처럼 Tahoe API 호출에 씁니다. |
id_token | 1시간 | 사용자에 대한 클레임이 담긴 서명된(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를 만나면 다시 가져옵니다. - 사용자마다 한 번에 하나씩 갱신하고, 새 토큰은 쓰기 전에 저장합니다.
- 로그아웃할 때 리프레시 토큰을 폐기합니다.
- 갱신이 실패하면 재시도하지 않고 사용자를 다시 로그인시킵니다.