Verify API
PASS식 1회성 SSAFY 인증
외부 앱의 로그인은 유지하면서 이번 거래에서 사용자가 SSAFY 구성원인지 확인합니다. 매 거래마다 Mattermost DM 코드 인증을 요구합니다.
Canonical issuer
Verify API의 issuer는 문서를 연 도메인이 아니라 아래 canonical 값으로 고정합니다.
Canonical issuer?- https://verify.myknow.xyz
Canonical API base URL?- https://verify.myknow.xyz
Canonical SDK URL?- https://verify.myknow.xyz/sdk/ssafy-verify.js
Compatibility alias?- none
Exact match rule?- callback.iss === "https://verify.myknow.xyz"
Flow summary
Verify API는 외부 앱 로그인과 별개로 이번 거래의 SSAFY 인증 여부만 확인합니다.
1. authorize
외부 앱이 state와 PKCE challenge를 만들고 사용자를 /verify/authorize로 보냄
2. consent
SSAFY Verify가 앱 이름, redirect domain, 제공 정보를 보여줌
3. DM code
사용자가 Mattermost DM으로 받은 6자리 코드를 입력
4. callback
외부 앱 redirect_uri로 code, state, iss 전달
5. token
외부 앱 서버가 /verify/token에서 verification_token 교환
6. verify
JWT 서명과 iss, aud, exp, sub, client_id, verified, auth_time, amr, acr 확인 후 최소 claim 저장
v1 공식 범위
Authorize
사용자를 SSAFY Verify 인증 화면으로 이동시키는 endpoint입니다.
Endpoint- GET /verify/authorize
Success callback- redirect_uri?code=...&state=...&iss=...
Failure callback- redirect_uri?error=...&error_code=...&request_id=...&state=...
Query parameters
client_id?- 필수. 승인된 Verify client id
redirect_uri?- 필수. client에 등록된 redirect URI와 exact match
scope?- 필수. ssafy.verify 포함. 예: ssafy.verify ssafy.affiliation ssafy.name
state?- 필수. 외부 앱이 생성하고 callback에서 비교하는 난수
code_challenge?- 필수. PKCE S256 challenge
code_challenge_method?- 필수. S256만 허용
nonce?- 선택. 거래 추적용 난수
authorize-url.txt브라우저 이동 URL 예시https://verify.myknow.xyz/verify/authorize?
client_id=client_example_public
&redirect_uri=https%3A%2F%2Fpartner.example.com%2Fssafy%2Fcallback
&scope=ssafy.verify%20ssafy.affiliation%20ssafy.name
&state=random_state_from_partner
&code_challenge=pkce_challenge
&code_challenge_method=S256Callback
사용자가 인증을 완료하면 SSAFY Verify가 등록된 redirect_uri로 이동합니다.
Success fields- code, state, iss
Failure fields- error, error_code, request_id, state
state- 외부 앱이 authorize 시작 시 만든 값과 반드시 비교
iss- https://verify.myknow.xyz 와 정확히 일치해야 token exchange 진행
code- 브라우저에 저장하지 않고 즉시 서버 route로 전달
Callback fields
code?- 성공 시 전달되는 1회성 callback code
state?- authorize 시작 시 외부 앱이 만든 값
iss?- SSAFY Verify issuer
error?- 실패 시 OAuth 스타일 error
error_code?- SSAFY Verify의 안정적인 내부 error code
request_id?- 실패 추적용 request id
callback-url.txt성공 callback 예시https://partner.example.com/ssafy/callback?
code=code_from_ssafy_verify
&state=random_state_from_partner
&iss=https%3A%2F%2Fverify.myknow.xyzToken
callback code를 verification_token JWT로 교환합니다. public client는 client_secret을 보내지 않습니다.
Endpoint- POST /verify/token
Content-Type- application/x-www-form-urlencoded
Cache- no-store 응답
Body parameters
grant_type?- 필수. verification_code
code?- 필수. callback query의 code
client_id?- 필수. 승인된 client id
client_secret?- confidential client에서만 서버에서 전송
code_verifier?- 필수. authorize 시작 시 만든 PKCE verifier
curl/verify/token 교환 요청curl -X POST https://verify.myknow.xyz/verify/token \
-H "content-type: application/x-www-form-urlencoded" \
-d "grant_type=verification_code" \
-d "client_id=client_example_public" \
-d "code=code_from_callback" \
-d "code_verifier=pkce_verifier_from_browser"success.json성공 응답{
"verification_token": "jwt_placeholder",
"token_type": "Bearer",
"expires_in": 300,
"scope": "ssafy.verify ssafy.affiliation ssafy.name",
"result": {
"verification_id": "verification_id_placeholder",
"verified": true,
"sub": "pairwise_subject_placeholder",
"auth_time": "2026-06-18T00:00:00.000Z"
}
}Success response fields
verification_token?- 서명된 Verify JWT
token_type?- Bearer
expires_in?- 초 단위 만료 시간
scope?- 승인되어 반영된 scope 문자열
result?- 검증 결과 요약 객체
auth_time 타입
error.json실패 응답{
"ok": false,
"error": {
"code": "PKCE_VERIFICATION_FAILED",
"message": "PKCE 검증에 실패했습니다.",
"request_id": "req_placeholder"
}
}Scope별 반환 claim
scope는 최소한으로 요청합니다. 이름과 프로필 이미지는 별도 scope입니다.
| Scope | Required | Claims | Nullable | 사용 조건 |
|---|---|---|---|---|
| ssafy.verify | yes | verified, sub, auth_time, verification_id, amr, acr | no | 모든 Verify 연동에 필요합니다. 외부 앱은 최소 인증 결과만 저장하세요. |
| ssafy.affiliation | no | ssafy_cohort, ssafy_campus, ssafy_region | yes | 기수, 캠퍼스, 지역 기준으로 혜택이나 권한을 나눌 때만 요청합니다. |
| ssafy.name | no | name | yes | 사용자 이름 표시나 기존 회원 정보 매칭이 필요할 때만 요청합니다. |
| ssafy.profile_image | no | picture | yes | 프로필 이미지를 화면에 표시할 때만 요청합니다. |
| ssafy.role | no | ssafy_role, ssafy_role_name | yes | 교육생/운영진 등 역할 기반 기능이 있을 때만 요청합니다. |
| ssafy.mattermost_id | no | ssafy_mattermost_user_id | yes | 기존 Mattermost 인증 프로젝트의 계정 매핑이 필요할 때만 요청합니다. |
Verification token claims
외부 앱 서버는 verification_token의 서명과 claim을 검증한 뒤 세션이나 DB에 최소 결과만 저장합니다.
iss?- SSAFY Verify issuer
aud?- client_id
sub?- client별 pairwise subject
exp?- 만료 시각. Verify token은 5분 이하
verification_id?- 이번 Verify 거래 id
verified?- 항상 true
auth_time?- JWT NumericDate seconds
amr?- mattermost_dm
acr?- urn:ssafy:verify:assurance:mattermost-team-dm:v1
ssafy_*?- 승인된 scope에 따라 제공되는 SSAFY claim
ssafy_mattermost_user_id?- Mattermost 고유 user id
Error JSON 경계
SSAFY Verify 원본 오류와 파트너 앱이 자기 frontend에 내려주는 응답 형식을 구분하세요.
SSAFY Verify original?- ok: false, error.code, error.message, error.request_id
Partner app response?- ok: false, errorCode, requestId
Security notes
- callback code와 verification_token은 로그에 남기지 않습니다.
- redirect URI는 등록값과 exact match여야 합니다.
- callback iss가 SSAFY Verify issuer와 다르면 token exchange를 시작하지 않습니다.
- PKCE verifier는 token exchange 직후 폐기합니다.
- 이름은 ssafy.name scope가 승인된 경우에만 사용합니다.
- 프로필 이미지는 ssafy.profile_image scope가 승인된 경우에만 사용합니다.
- JWT는 iss, aud, exp, sub, client_id, verified, auth_time, amr, acr를 모두 검증합니다.
- 기존 Mattermost 인증 계정 매핑이 필요할 때만 ssafy.mattermost_id를 요청합니다.
- Mattermost user id는 raw user object의 id이며, username은 변경 가능한 보조 표시값으로만 취급합니다.
- cohort는 raw user object가 아니라 수집한 Mattermost team context 기준으로 판단합니다.