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

모바일 인증 요청은 embedded WebView에서 열지 않습니다. 시스템 브라우저, Universal Link, verified App Link, backend exchange를 사용하세요.

client secret 금지

브라우저와 모바일 앱에는 client_secret을 넣지 않습니다. confidential client를 쓰는 경우에도 앱 서버 환경변수에만 둡니다.

적용 흐름

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 검증

1

지원 범위 확인

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 timeout
2

PKCE와 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 padding
3

authorize와 callback 계약

모바일 앱은 hosted page URL을 시스템 브라우저로 열고 callback query만 처리합니다.

붙일 위치

모바일 앱의 인증 시작 로직

확인 방법

callback 성공 필드가 code, state, iss로 제한됨

mobile-authorize-contract.txt모바일 앱이 구현할 Verify redirect contract
GET 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_app
4

deep 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 registration
5

iOS 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"
          }
        ]
      }
    ]
  }
}
6

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"
      ]
    }
  }
]
7

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 인증 계정 매핑 목적일 때만 요청하고 저장