Backend Guide

Backend token exchange 공통 구현

모든 웹/모바일 앱은 자기 backend에서 /verify/token 교환과 verification_token 검증을 수행합니다.

이 문서가 맞는 경우

플랫폼별 권장 흐름을 먼저 고정한 뒤, code 교환은 모두 앱 서버에서 처리합니다.

Best for
Next.js Route Handler, Nuxt server route, Express, Spring, Django, Rails 등
Strategy
Client callback code + PKCE verifier -> backend -> SSAFY Verify /verify/token
Callback
client는 code, codeVerifier, redirectUri, iss를 자기 backend로 보내고 backend가 issuer와 JWT를 검증합니다.
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으로 교환

client secret 금지

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

적용 흐름

client request

callback state와 iss를 확인한 뒤 code와 codeVerifier를 backend로 전송

input validation

backend가 code, codeVerifier, redirectUri, iss를 검증

token exchange

backend가 /verify/token에 grant_type=verification_code로 교환

JWT validation

JWKS로 verification_token 서명과 iss, aud, exp, sub, client_id, verified, auth_time, amr, acr를 검증

session save

최소 claim만 앱 session 또는 DB에 저장

1

client to backend 요청 계약

브라우저와 모바일 앱이 자기 backend로 보내는 최소 JSON입니다. client_secret은 포함하지 않습니다.

붙일 위치

외부 앱의 /api/ssafy/verify-token 같은 endpoint

확인 방법

iss가 SSAFY Verify issuer와 다르면 즉시 거절

client-to-backend.schema.jsonclient callback 결과를 backend로 전달하는 계약
{
  "method": "POST",
  "endpoint": "/api/ssafy/verify-token",
  "contentType": "application/json",
  "body": {
    "code": "code_from_callback",
    "codeVerifier": "pkce_verifier_from_client",
    "redirectUri": "https://partner.example.com/ssafy",
    "iss": "https://verify.myknow.xyz"
  },
  "validation": {
    "code": "required string",
    "codeVerifier": "required string",
    "redirectUri": "must be one of registered callback URLs",
    "iss": "must equal https://verify.myknow.xyz"
  }
}
2

SSAFY Verify token exchange

backend가 SSAFY Verify와 직접 통신합니다. confidential client secret은 이 단계에서만 서버 환경변수로 사용합니다.

붙일 위치

외부 앱 backend service

확인 방법

grant_type은 verification_code이고 response는 no-store로 처리

backend-to-ssafy-verify.txt/verify/token form body 계약
POST https://verify.myknow.xyz/verify/token
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-store

grant_type=verification_code
client_id=client_example_public
code=code_from_callback
code_verifier=pkce_verifier_from_client

optional confidential client field:
  client_secret is read from server env only
3

verification_token 검증

token payload를 믿기 전에 JWKS 서명과 필수 claim을 모두 검증합니다.

붙일 위치

외부 앱 backend token validation module

확인 방법

issuer, audience, expiration, subject, client_id, verified, auth_time, amr, acr가 모두 검증됨

verification-token-validation.ymlJWT 검증 필수 조건
verification_token validation:
  jwks: https://verify.myknow.xyz/verify/jwks
  required:
    iss: https://verify.myknow.xyz
    aud: client_example_public
    exp: must be in the future
    sub: required pairwise subject
    client_id: client_example_public
    verified: true
    auth_time: NumericDate seconds
    amr: contains mattermost_dm
    acr: urn:ssafy:verify:assurance:mattermost-team-dm:v1
  reject_if:
    signature invalid
    token expired
    issuer mismatch
    audience mismatch
    verified is not true
4

실패 응답 매핑

SSAFY Verify의 error_code와 request_id를 보존하되 token/code/secret 원문은 public response에 포함하지 않습니다.

붙일 위치

외부 앱 backend error mapper

확인 방법

사용자 화면에는 안전한 message와 request_id만 표시

backend-error-mapping.jsonpartner app public error response 예시
{
  "ssafyVerifyError": {
    "ok": false,
    "error": {
      "code": "PKCE_VERIFICATION_FAILED",
      "message": "PKCE 검증에 실패했습니다.",
      "request_id": "req_placeholder"
    }
  },
  "partnerAppResponse": {
    "ok": false,
    "errorCode": "PKCE_VERIFICATION_FAILED",
    "requestId": "req_placeholder",
    "message": "인증 요청을 다시 시작해주세요."
  }
}

공통 서버 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만 공식 지원

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