Platform Guide
모바일 앱 SSAFY 인증 공식 연동 범위
모바일 공식 지원은 SDK 유지보수가 아니라 hosted verification page, protocol, backend exchange contract입니다. WebView 인증은 지원하지 않습니다.
이 문서가 맞는 경우
플랫폼별 권장 흐름을 먼저 고정한 뒤, code 교환은 모두 앱 서버에서 처리합니다.
Best for- iOS, Android, Flutter, React Native 등 모든 모바일 앱
Strategy- System browser + deep link callback + backend token exchange
Callback- Universal Link 또는 verified App Link를 우선 사용하고 custom scheme은 대체 수단으로만 사용합니다.
Support- 공식 지원: protocol, hosted page, backend exchange contract
준비물
아래 값이 준비되면 공식 지원 흐름에 맞춰 backend token exchange를 연결할 수 있습니다.
issuer
모든 플랫폼에서 canonical issuer/API base URL은 https://verify.myknow.xyz 입니다
client_id
Developer Portal에서 승인된 public 또는 confidential client id
redirect_uri
client 설정에 exact match로 등록된 callback URL 또는 deep link
scope
ssafy.verify는 필수. 기수/캠퍼스/지역은 ssafy.affiliation, 이름은 ssafy.name, 이미지는 ssafy.profile_image 추가
Mattermost id
기존 Mattermost 인증 프로젝트의 계정 매핑이 필요한 경우에만 ssafy.mattermost_id 추가
backend endpoint
앱 서버에서 code와 code_verifier를 받아 /verify/token으로 교환
WebView unsupported
client secret 금지
적용 흐름
authorize URL
앱이 state와 PKCE challenge를 만들고 hosted page를 시스템 브라우저로 열기
hosted page
SSAFY Verify가 동의, Mattermost DM 코드, 인증 완료 화면을 처리
deep link
등록된 redirect_uri로 code, state, iss만 앱에 전달
backend exchange
앱 서버가 /verify/token으로 교환하고 verification_token 검증
지원 범위 확인
SSAFY Verify가 책임지는 범위와 각 앱이 책임지는 범위를 먼저 분리합니다.
붙일 위치
모바일 앱 연동 설계 문서 또는 구현 이슈
확인 방법
WebView와 앱 내 client_secret 사용이 제외되어 있음
mobile-support-policy.yml공식 지원 범위와 unsupported 항목official_support:
- Verify API protocol
- SSAFY Verify hosted verification page
- /verify/token backend exchange contract
- verification_token claim contract
best_effort_reference:
- platform deep link examples
- system browser launch examples
unsupported:
- embedded WebView authorization
- persistent or long-term storage of code, token, code_verifier, or client_secret in the app
- mobile app direct /verify/token exchange with client_secret
allowed_short_lived_transaction_state:
- state in app memory or short-lived encrypted transaction storage
- code_verifier in the same transaction state until callback/backend exchange
- discard state and code_verifier immediately after backend exchange or timeoutPKCE와 state 수명 관리
모바일 앱은 state와 code_verifier를 같은 거래 상태로 묶고, callback 검증 후 backend exchange까지만 보관합니다.
붙일 위치
모바일 앱의 인증 transaction 상태 관리 로직
확인 방법
state 불일치 callback은 backend로 전달되지 않음
mobile-pkce-state.ymlPKCE S256과 state 보관 규칙create mobile verify transaction:
state:
generate: cryptographically secure random string, 32+ chars
store: app memory or short-lived encrypted transaction state only
never_store: persistent storage, analytics, crash logs, or long-term cache
ttl: 5 minutes or less
compare: callback state must exactly equal stored state
code_verifier:
generate: PKCE allowed chars, 43-128 chars
store: same short-lived transaction state as state
never_store: persistent storage, analytics, crash logs, or long-term cache
send_to_backend: only after callback state matches
discard: after backend exchange finishes
code_challenge:
method: S256
value: base64url(sha256(code_verifier)) without paddingauthorize와 callback 계약
모바일 앱은 hosted page URL을 시스템 브라우저로 열고 callback query만 처리합니다.
붙일 위치
모바일 앱의 인증 시작 로직
확인 방법
callback 성공 필드가 code, state, iss로 제한됨
mobile-authorize-contract.txt모바일 앱이 구현할 Verify redirect contractGET https://verify.myknow.xyz/verify/authorize
required query:
client_id=client_example_public
redirect_uri=https://partner.example.com/mobile/ssafy/callback
scope=ssafy.verify ssafy.affiliation ssafy.name
state=random_state_from_app
code_challenge=pkce_s256_challenge
code_challenge_method=S256
success callback:
redirect_uri?code=code_from_ssafy_verify&state=random_state_from_app&iss=https%3A%2F%2Fverify.myknow.xyz
failure callback:
redirect_uri?error=access_denied&error_code=CONSENT_DENIED&request_id=req_placeholder&state=random_state_from_appdeep link 등록 최소 예시
Universal Link와 verified App Link를 우선 등록합니다. custom scheme은 대체 수단으로만 검토합니다.
붙일 위치
iOS Associated Domains, Android intent-filter, hosting .well-known files
확인 방법
등록된 redirect_uri와 실제 deep link URL이 exact match
mobile-deep-link-contract.yml모바일 callback URL 등록 기준iOS Universal Link:
redirect_uri: https://partner.example.com/ios/ssafy/callback
associated_domains: applinks:partner.example.com
association_file: https://partner.example.com/.well-known/apple-app-site-association
Android verified App Link:
redirect_uri: https://partner.example.com/android/ssafy/callback
intent_filter: scheme=https, host=partner.example.com, pathPrefix=/android/ssafy/callback
association_file: https://partner.example.com/.well-known/assetlinks.json
Fallback custom scheme:
redirect_uri: partnerapp://ssafy/callback
use_only_when: Universal Link or App Link cannot be used
review: collision risk and exact redirect URI registrationiOS association file 예시
Universal Link를 쓰는 iOS 앱은 partner domain에 apple-app-site-association 파일을 제공해야 합니다.
붙일 위치
https://partner.example.com/.well-known/apple-app-site-association
확인 방법
Content-Type은 application/json 또는 application/pkcs7-mime로 제공
apple-app-site-associationiOS Universal Link domain association placeholder{
"applinks": {
"apps": [],
"details": [
{
"appIDs": [
"TEAMID.com.example.partner"
],
"components": [
{
"/": "/ios/ssafy/callback"
}
]
}
]
}
}Android asset links 예시
verified App Link를 쓰는 Android 앱은 partner domain에 assetlinks.json을 제공해야 합니다.
붙일 위치
https://partner.example.com/.well-known/assetlinks.json
확인 방법
package_name과 인증서 fingerprint가 실제 앱 값과 일치
assetlinks.jsonAndroid App Link domain association placeholder[
{
"relation": [
"delegate_permission/common.handle_all_urls"
],
"target": {
"namespace": "android_app",
"package_name": "com.example.partner",
"sha256_cert_fingerprints": [
"SHA256_CERT_FINGERPRINT_PLACEHOLDER"
]
}
}
]backend exchange 계약
모바일 앱은 자기 서버로 code와 code_verifier만 보내고, 서버가 SSAFY Verify와 교환합니다.
붙일 위치
앱 서버의 /api/ssafy/verify-token 같은 endpoint
확인 방법
앱에는 client_secret과 verification_token 원문이 저장되지 않음
mobile-backend-contract.json모바일 앱과 backend 사이의 최소 계약{
"mobileApp": {
"sendsToOwnBackend": {
"code": "code_from_callback",
"codeVerifier": "pkce_verifier_from_app_memory",
"redirectUri": "https://partner.example.com/mobile/ssafy/callback",
"iss": "https://verify.myknow.xyz"
}
},
"backend": {
"exchangesWithSsafyVerify": {
"endpoint": "https://verify.myknow.xyz/verify/token",
"contentType": "application/x-www-form-urlencoded",
"grant_type": "verification_code",
"client_id": "client_example_public",
"code": "code_from_callback",
"code_verifier": "pkce_verifier_from_app_memory"
}
}
}공통 서버 token exchange 계약
플랫폼이 무엇이든 최종 교환과 verification_token 검증은 앱 서버에서 수행합니다.
Client request- 앱 또는 브라우저가 자기 backend로 보내는 JSON
Backend action- backend가 /verify/token에 grant_type=verification_code로 교환
Backend response- 앱 세션에 저장해도 되는 최소 인증 결과만 반환
client-to-backend.json앱이 자기 서버에 보내는 body{
"code": "code_from_callback",
"codeVerifier": "pkce_verifier_from_client",
"redirectUri": "https://partner.example.com/ssafy",
"iss": "https://verify.myknow.xyz"
}Client request fields
code?- callback으로 받은 1회성 Verify code
codeVerifier?- PKCE 검증에 필요한 원본 verifier
redirectUri?- 외부 앱에 등록된 callback URL
iss?- callback 응답의 issuer
backend-success.json앱 서버가 클라이언트에 돌려주는 최소 결과{
"ok": true,
"verified": true,
"sub": "pairwise_subject_placeholder",
"cohort": "15",
"campus": "서울 캠퍼스",
"authTime": 1781740800
}Backend response fields
ok?- 외부 앱 backend 처리 성공 여부
verified?- SSAFY 구성원 인증 완료 여부
sub?- client별 pairwise subject
cohort?- SSAFY 기수
campus?- SSAFY 캠퍼스
authTime?- JWT auth_time NumericDate seconds
보안 체크리스트
공식 지원 범위
protocol, hosted page, backend token exchange만 공식 지원
WebView
Unsupported / Do not use. 인증 요청을 embedded WebView에서 열지 않음
secret 위치
client_secret은 confidential client의 서버 환경변수에만 저장
state 검증
authorize 시작 시 만든 state와 callback state를 반드시 비교
PKCE 보관
code_verifier는 callback 완료 후 서버 교환까지만 짧게 보관
token 검증
verification_token은 서버에서 iss, aud, exp, sub, client_id, verified, auth_time, amr, acr를 검증
저장 최소화
앱 DB에는 verified, sub, cohort, campus, verifiedAt 정도만 저장
Mattermost id
ssafy.mattermost_id는 기존 MM 인증 계정 매핑 목적일 때만 요청하고 저장