Theory
security
Apple & Goole OAuth 차이점

Google OAuth vs Apple OAuth 완벽 가이드

목차

  1. 기본 OAuth 흐름 차이
  2. Client Secret 생성 방식 차이
  3. Authorization Code Flow 상세 설명
  4. 보안 Best Practices
  5. 참고 문서

1. 기본 OAuth 흐름 차이

Google OAuth

특징

  • 리프레시 토큰: 항상 제공
  • 사용자 정보: 매번 조회 가능
  • 토큰 갱신: 리프레시 토큰으로 언제든지 재발급
  • 유연성: 높음

동작 방식

  1. 사용자가 Google 계정으로 로그인
  2. Authorization Code 발급
  3. Access Token + Refresh Token 발급
  4. 사용자 정보 조회 (UserInfo API)
  5. Access Token 만료 시 Refresh Token으로 재발급
  6. 필요할 때마다 사용자 정보 재조회 가능

Apple OAuth

특징

  • 리프레시 토큰: 최초 로그인 시에만 제공
  • 사용자 정보: 최초 로그인 시에만 제공
  • 토큰 갱신: 리프레시 토큰이 없으면 재로그인 필요
  • 보안 중심: 사용자 프라이버시를 더 엄격하게 보호

동작 방식

  1. 사용자가 Apple 계정으로 로그인
  2. Authorization Code 발급
  3. 최초 로그인 시: Access Token + Refresh Token + User Info 제공
  4. 재로그인 시: Access Token만 제공 (Refresh Token, User Info 없음)
  5. Refresh Token이 없으면 사용자 재로그인 필요

핵심 차이

항목GoogleApple
리프레시 토큰매번 제공최초 1회만
사용자 정보매번 조회 가능최초 1회만
데이터 관리유연함반드시 최초 저장 필수

2. Client Secret 생성 방식 차이

핵심 요약

항목AppleGoogle
생성 방식동적 JWT 생성정적 문자열
알고리즘ES256 (ECDSA)없음 (평문)
암호화 방식비대칭 (Public/Private Key)대칭 (Shared Secret)
유효기간최대 6개월 (권장 1시간)영구
보안성높음중간

Apple OAuth: 동적 Client Secret (JWT)

생성 방식

JWT를 **ES256 알고리즘 (ECDSA with SHA-256)**으로 서명하여 생성합니다.

출처: scottbrady.io (opens in a new tab)
"Apple does not support shared secrets for client authorization, instead using a custom implementation similar to JWT Bearer Token for Client Authentication where developers must generate and sign their own credentials."

JWT 구조

Header.Payload.Signature
  • Header: {"alg": "ES256", "kid": "ABC123DEF4"}
  • Payload: {"iss": "TEAM_ID", "aud": "https://appleid.apple.com", "sub": "CLIENT_ID", "exp": 1516242622}
  • Signature: Private Key로 서명한 값

필요한 정보

4가지 필수 파라미터

  • Team ID: Apple Developer 계정 식별자
  • Client ID: Service Identifier (예: com.example.app)
  • Key ID: Private Key 식별자
  • Private Key: .p8 파일 형태의 ECDSA Private Key

JWT 필수 클레임

출처: bannister.me (opens in a new tab)
"The client secret JWT must include four mandatory claims:

코드 예시

public getClientSecret(): string {
  return appleSignin.getClientSecret({
    clientID: this.clientId,        // Service Identifier
    keyIdentifier: this.keyId,      // Key ID
    teamID: this.teamId,            // Team ID
    privateKey: this.privateKey,    // .p8 파일의 Private Key
  });
}

보안 이점

비대칭 암호화

출처: criipto.com (opens in a new tab)
"Private Key JWT significantly reduces the attack surface for credential theft by using asymmetric cryptography where your application keeps a private key to itself and shares only its corresponding public key with the authorization server. The private key never leaves your application."

재생 공격 방지

출처: criipto.com (opens in a new tab)
"Private Key JWT uses specific JWT claims (like jti for unique IDs and exp for expiration) to prevent replay attacks, and has the security advantage of shorter lifetime of the client_assertion—usually a maximum of an hour."

권장 사항: 매 요청마다 생성

출처: bannister.me (opens in a new tab)
"A better approach is to generate the JWT for your client secret on each request. It is very fast to generate and will not add any noticeable performance decrease to your requests."

Google OAuth: 정적 Client Secret

생성 방식

Google API Console에서 OAuth 클라이언트 생성 시 자동으로 발급됩니다.

출처: Google Cloud Console Help (opens in a new tab)
"Visiting the Google API Console to obtain OAuth 2.0 credentials such as a client ID and client secret that are known to both Google and your application."

코드 예시

// 환경 변수에서 한 번만 로드
private readonly clientSecret: string;
 
constructor() {
  this.clientSecret = process.env.GOOGLE_CLIENT_SECRET; // 정적 값
}
 
// OAuth 요청 시 그대로 사용
async exchangeCodeForTokens(code: string) {
  const response = await httpClient.post('https://oauth2.googleapis.com/token', {
    client_id: this.clientId,
    client_secret: this.clientSecret,  // 평문 문자열
    code: code,
    grant_type: 'authorization_code',
    redirect_uri: this.redirectUri,
  });
}

보안 고려사항

유출 시 위험

출처: criipto.com (opens in a new tab)
"If the secret is leaked, intercepted, or stolen, anyone can impersonate your application, and client secrets are usually long-lived and sent in plaintext over the wire."

서버 환경에서만 사용

출처: oauth.net (opens in a new tab)
"The client secret must be kept confidential. If a deployed app cannot keep the secret confidential, such as single-page Javascript apps or native apps, then the secret is not used."


3. Authorization Code Flow 상세 설명

전체 흐름 (시퀀스)

[사용자] → [클라이언트 앱] → [Apple/Google 로그인] → [Authorization Code 발급] 
→ [백엔드 서버] → [Token 교환 요청] → [Apple/Google 검증] → [Access Token 발급]

상세 단계

  1. 사용자가 "Apple/Google로 로그인" 버튼 클릭
  2. 클라이언트가 사용자를 Authorization Server로 리다이렉트
  3. 사용자가 인증 (Face ID, 비밀번호 등)
  4. Authorization Server가 Authorization Code 생성
  5. Redirect URI로 사용자를 리다이렉트 (Code 포함)
  6. 클라이언트가 Code를 백엔드 서버로 전송
  7. 백엔드가 Code를 Token Endpoint로 전송 (+ Client Secret)
  8. Authorization Server가 검증 후 Access Token, Refresh Token, ID Token 발급

Authorization Code란?

특성

출처: RFC 6749 - Section 4.1.2 (opens in a new tab)
"The authorization code MUST expire shortly after it is issued to mitigate the risk of leaks. A maximum authorization code lifetime of 10 minutes is RECOMMENDED."

출처: Apple Developer Documentation (opens in a new tab)
"The authorization code has a five (5) minute expiry and is restricted to single-use."

보안 특성

  • 유효기간: Apple 5분, OAuth 표준 최대 10분
  • Single-use: 한 번만 사용 가능, 재사용 시 모든 토큰 무효화
  • Opaque String: 읽을 수 없는 불투명한 문자열
  • Client-specific: 발급받은 클라이언트만 사용 가능

Code에 연결된 정보 (서버 내부 저장)

  • 사용자 식별자
  • client_id
  • redirect_uri
  • scope
  • 발급 시간
  • 사용 여부

Token 교환 과정

요청 예시

POST https://appleid.apple.com/auth/token
Content-Type: application/x-www-form-urlencoded
 
client_id=com.example.app
&client_secret=eyJhbGciOiJFUzI1NiIsImtpZCI6IkFCQzEyMyJ9...
&code=c1234567890abcdef
&grant_type=authorization_code
&redirect_uri=https://example.com/callback

Apple/Google이 검증하는 항목

출처: RFC 6749 - Section 4.1.3 (opens in a new tab)
"The authorization server MUST:

  • require client authentication for confidential clients
  • authenticate the client if client authentication is included
  • ensure that the authorization code was issued to the authenticated client
  • verify that the authorization code is valid
  • ensure that the redirect_uri parameter is present if included in the initial request"

1. client_id 검증

  • 등록된 앱인지 확인

2. client_secret 검증

Apple의 경우 (JWT):

  • JWT 구조 파싱 (Header, Payload, Signature)
  • 알고리즘이 ES256인지 확인
  • JWT Header의 kid로 Public Key 조회
  • Public Key로 서명 검증
  • Claims 검증:

Google의 경우 (평문):

  • 데이터베이스에 저장된 Secret과 일치하는지 확인

3. code 유효성 검증

  • 데이터베이스에서 code 조회
  • 만료되지 않았는지 확인 (5분 이내)
  • 이전에 사용되지 않았는지 확인
  • 요청한 client_id와 code 발급 시 client_id 일치 확인

4. redirect_uri 검증

  • Code 발급 시 사용된 redirect_uri와 동일한지 확인

Apple의 JWT 서명 검증 상세

Public Key 획득

출처: Apple Developer Documentation (opens in a new tab)
"Apple's public keys are available at https://appleid.apple.com/auth/keys (opens in a new tab)"

Apple은 개발자가 Private Key를 생성할 때 Public Key를 내부 데이터베이스에 저장합니다.

검증 과정

출처: Sarunw (opens in a new tab)
"Use the kid claim from the JWT header to determine which key you need. Your backend has to check the header to get the correct kid to get the correct key from the auth/keys endpoint."

  1. JWT 파싱: Header.Payload.Signature로 분리
  2. kid 추출: JWT Header에서 Key ID 추출
  3. Public Key 선택: Apple 데이터베이스에서 해당 kid의 Public Key 조회
  4. 서명 검증:
    • Header + Payload를 Base64URL 디코딩
    • ECDSA P-256 곡선 + SHA-256 해시 알고리즘 사용
    • Public Key로 Signature 검증
    • 서명이 유효하면 해당 Private Key로 서명되었음을 증명

응답 (Token)

{
  "access_token": "beg3456...67Or9",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "abc789...xyz123",
  "id_token": "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ..."
}

출처: Medium - Apple Sign-In (opens in a new tab)
"access_token - A token reserved for future use; currently no data set has been defined for access, valid for an hour
id_token - A JSON Web Token that contains the user's identity information
refresh_token - The refresh token used to regenerate new access tokens"

토큰 설명

  • access_token: 현재 미래 사용 예약, 1시간 유효
  • id_token: 사용자 신원 정보 (이메일, sub, name 등)
  • refresh_token: Access Token 갱신용
  • expires_in: 3600초 (1시간)

왜 Authorization Code를 사용하는가?

1. 브라우저 히스토리 보호

출처: StackOverflow (opens in a new tab)
"The Authorization Code Grant Type keeps sensitive information from the browser history. When the authorization server redirects back to the application, the URL of this redirect ends up in the browser history, so using a short-lived code instead of the actual token prevents the access token from being logged."

2. 중간자 공격 방지

출처: Auth0 (opens in a new tab)
"The client app, (browser or native app), can have the delivered token intercepted. In order to reduce this threat, short-lived authorization codes are passed instead of tokens and exchanged for tokens over a more secure direct connection between client and authorization server."

3. XSS/Malware 위험 감소

출처: OAuth 2.0 Simplified (opens in a new tab)
"The access token is never visible to the user or their browser, so it is the most secure way to pass the token back to the application, reducing the risk of the token leaking to someone else."

왜 Client Secret이 필요한가?

출처: StackOverflow (opens in a new tab)
"Even if the attacker stole the authorization code the attacker would not be able to create an access token because the attacker would need to have access to your client_secret too."

핵심 이유

  1. 클라이언트 인증: Code만으로는 누가 요청하는지 알 수 없음
  2. Code 탈취 방지: Code를 가로채도 Secret 없이는 Token 획득 불가
  3. 2단계 보안: Code + Secret 조합으로 보안 강화

왜 Redirect URI를 검증하는가?

출처: Information Security StackExchange (opens in a new tab)
"Redirect URI validation prevents OAuth providers from being used as phishing vectors, as the redirect_uri is an address used by OAuth providers to deliver the access_token via browser redirect."

핵심 이유

  1. Code 탈취 방지: 공격자가 자신의 redirect_uri로 Code를 받으려는 시도 차단
  2. 피싱 방지: 악의적인 앱이 정상 앱으로 위장하는 것 방지
  3. Open Redirect 공격 방지: 임의의 URL로 리다이렉트하는 것 차단

4. 보안 Best Practices

1. PKCE (Proof Key for Code Exchange) 사용

출처: RFC 9126 (opens in a new tab)
"One of the key recommendations from OAuth 2.0 Security Best Current Practice (RFC 9126) is to use PKCE for all OAuth clients, including confidential clients, to protect against code interception attacks."

PKCE 추가 파라미터

  • code_challenge: SHA256(code_verifier)의 Base64URL 인코딩
  • code_challenge_method: "S256"
  • code_verifier: 랜덤 생성된 43-128자 문자열

2. State 파라미터로 CSRF 방지

Authorization 요청 시 랜덤 state 값을 생성하고, 콜백에서 동일한 값이 돌아오는지 검증합니다.

3. Nonce 사용 (OpenID Connect)

id_token의 nonce claim을 검증하여 재생 공격을 방지합니다.

4. HTTPS 필수

모든 OAuth 2.0 통신은 반드시 HTTPS를 사용해야 합니다.

5. Token 저장 보안

  • Refresh Token: 서버 데이터베이스에 암호화하여 저장
  • Access Token: 메모리 또는 암호화된 쿠키에 저장
  • 절대 금지: localStorage에 토큰 저장 (XSS 취약)

5. 참고 문서

OAuth 2.0 표준

Apple Sign In

Google OAuth

보안


요약 테이블

기본 OAuth 흐름 비교

항목GoogleApple
리프레시 토큰매번 제공최초 1회만
사용자 정보매번 조회 가능최초 1회만
구현 난이도쉬움중간 (최초 저장 필수)

Client Secret 비교

항목GoogleApple
타입정적 문자열동적 JWT
알고리즘없음ES256 (ECDSA)
암호화대칭 (Shared Secret)비대칭 (Public/Private Key)
유효기간영구최대 6개월 (권장 1시간)
보안성중간높음
구현 복잡도낮음높음

Authorization Code 비교

항목OAuth 표준Apple
유효기간최대 10분 권장5분
사용 횟수1회1회
재사용 시에러모든 토큰 무효화