OAuth2 dPoP Delphi

· 기능

OAuth 2.0 액세스 토큰은 API 왕국의 열쇠예요. 누군가 토큰을 훔치면 어디서든 사용할 수 있어요. RFC 9449에 정의된 DPoP(Demonstrating Proof of Possession)는 토큰을 요청한 클라이언트에 암호학적으로 바인딩해서 이 문제를 해결해요. 토큰이 가로채지더라도 클라이언트의 개인 키 없이는 쓸모없어요.

sgcWebSockets 2026.4.0부터 OAuth2 클라이언트와 서버 컴포넌트에 완전한 DPoP 지원이 포함돼요. 이 글은 DPoP가 무엇인지, 어떻게 작동하는지, Delphi 애플리케이션에서 어떻게 설정하는지 설명해요.

DPoP란 무엇인가요?

표준 OAuth 2.0에서 Bearer 토큰은 현금처럼 작동해요. 토큰을 가진 사람이면 누구든 사용할 수 있어요. 공격자가 손상된 네트워크, 유출된 로그, 악성 프록시를 통해 토큰을 가로채면 보호된 리소스에 완전한 접근 권한을 얻어요.

DPoP는 토큰이 PIN이 있는 신용카드처럼 작동하도록 변경해요. 토큰 자체가 공개 키에 바인딩되고, 모든 요청에 해당 개인 키로 서명된 증명 JWT가 포함돼야 해요. 서버는 요청을 수락하기 전에 증명이 바인딩된 키와 일치하는지 검증해요.

Bearer 토큰 (전통적 방식)
토큰을 가진 사람이라면 누구든 사용할 수 있어요. 훔친 토큰은 완전히 악용 가능해요. 신원 증명이 필요 없어요.
DPoP 토큰 (발신자 제한)
토큰이 키 쌍에 바인딩돼요. 모든 요청에 새로운 서명 증명이 필요해요. 개인 키 없이는 훔친 토큰이 쓸모없어요.

DPoP 작동 방식

DPoP 흐름은 표준 OAuth2 프로세스에 경량 암호화 단계를 추가해요:

1. 키 생성 — 클라이언트가 비대칭 키 쌍(ES256 또는 RS256)을 생성해요. 개인 키는 클라이언트에 유지되고, 공개 키는 DPoP 증명에 JWK로 포함돼요.

2. 토큰 요청 — 액세스 토큰을 요청할 때 클라이언트는 서명된 JWT 증명이 포함된 DPoP HTTP 헤더를 포함해요. 증명에는 HTTP 메서드, URL, 타임스탬프, 고유 식별자가 포함돼요.

3. 토큰 바인딩 — 권한 부여 서버가 증명 서명을 검증하고, JWK 썸프린트(공개 키의 SHA-256 해시)를 추출해 발급된 토큰에 바인딩해요. 응답에는 Bearer 대신 token_type: DPoP가 포함돼요.

4. 리소스 접근 — 모든 API 호출에서 클라이언트는 토큰과 새로운 DPoP 증명을 함께 전송해요. 증명에는 ath 클레임(액세스 토큰의 SHA-256 해시)이 포함되어 증명을 해당 특정 토큰에 바인딩해요.

DPoP 증명 내부

DPoP 증명은 헤더, 페이로드, 서명의 세 부분으로 구성된 컴팩트 JWT예요.

// Header - identifies the key and algorithm
{
  "typ": "dpop+jwt",
  "alg": "ES256",
  "jwk": {
    "kty": "EC",
    "crv": "P-256",
    "x": "cWs37kZLJMej6fpd...",
    "y": "e2bkcQGaBERgSZUb..."
  }
}
// Payload - proves freshness and binds to the request
{
  "htm": "POST",
  "htu": "https://auth.example.com/oauth/token",
  "iat": 1774950263,
  "jti": "F1AFCD1F-95F7-401B-A2F5-195A31DB1802",
  "ath": "fUHyO2r2Z3DZ53EsNr..."
}

sgcWebSockets에서 DPoP 설정하기

1단계: ES256 키 쌍 생성

OpenSSL을 사용해 타원 곡선 키 쌍을 생성하세요:

# Generate EC private key
openssl ecparam -name prime256v1 -genkey -noout -out dpop_private.pem
# Extract public key parameters
openssl ec -in dpop_private.pem -text -noout

공개 키 X와 Y 좌표를 Base64URL로 변환해 JWK를 만드세요:

{"kty":"EC","crv":"P-256","x":"cWs37kZLJMej6fpdyKaI8Gz6CE...","y":"e2bkcQGaBERgSZUbAGR-iOOM..."}

2단계: OAuth2 클라이언트 설정

// Configure OAuth2 as usual
OAuth2.OAuth2Options.GrantType := auth2CodePKCE;
OAuth2.OAuth2Options.ClientId := 'your-client-id';
OAuth2.OAuth2Options.ClientSecret := 'your-client-secret';
OAuth2.AuthorizationServerOptions.AuthURL := 'https://auth.example.com/authorize';
OAuth2.AuthorizationServerOptions.TokenURL := 'https://auth.example.com/oauth/token';
// Enable DPoP
OAuth2.DPoPOptions.Enabled := True;
OAuth2.DPoPOptions.Algorithm := dpopES256;
OAuth2.DPoPOptions.PrivateKey.LoadFromFile('dpop_private.pem');
OAuth2.DPoPOptions.PublicKeyJWK := '{"kty":"EC","crv":"P-256","x":"...","y":"..."}';
// Start the OAuth2 flow - DPoP headers are added automatically
OAuth2.Start;

이게 전부예요. 컴포넌트가 자동으로 다음을 수행해요:

3단계: API 호출에 DPoP 증명 사용

DPoP 바인딩된 액세스 토큰을 얻은 후 각 API 요청에 대한 증명을 생성하세요:

var
  vProof: String;
begin
  // Generate a DPoP proof for the API call
  vProof := OAuth2.GetDPoPProof(
    'GET',
    'https://api.example.com/userinfo',
    OAuth2.AccessToken
  );
  // Include both headers in your HTTP request:
  //   Authorization: DPoP <access_token>
  //   DPoP: <proof_jwt>
  HTTPClient.Request.CustomHeaders.AddValue('Authorization',
    'DPoP ' + OAuth2.AccessToken);
  HTTPClient.Request.CustomHeaders.AddValue('DPoP', vProof);
  HTTPClient.Get('https://api.example.com/userinfo');
end;

지원 공급자

DPoP는 이미 주요 OAuth2 공급자에서 지원돼요:

Auth0 애플리케이션 설정에서 DPoP를 활성화하세요. nonce 지원이 필요해요(자동으로 처리됨).
Okta 권한 부여 서버 접근 정책에서 DPoP를 설정하세요. 2024년부터 GA예요.
Microsoft Entra ID 기밀 클라이언트에 대한 DPoP를 지원해요.
Ping Identity PingOne 및 PingFederate에서 완전한 DPoP 지원을 제공해요.

서버 측 DPoP 검증

sgcWebSockets OAuth2 서버 컴포넌트도 DPoP 검증을 지원해요. OAuth2Options.DPoP가 활성화되면 서버가 자동으로 다음을 수행해요:

// Enable DPoP on the OAuth2 server
OAuth2Server.OAuth2Options.DPoP := True;
// Optional: custom validation via event
OAuth2Server.OnOAuth2ValidateDPoP := procedure(Sender: TObject;
  Connection: TsgcWSConnection;
  const DPoPProof, AccessToken: String;
  var IsValid: Boolean)
begin
  // Add custom checks here (e.g., verify against a key registry)
  IsValid := True;
end;

DPoP API 참조

속성 / 메서드 설명
DPoPOptions.Enabled 모든 토큰 요청에 DPoP를 활성화해요.
DPoPOptions.Algorithm dpopES256(권장) 또는 dpopRS256.
DPoPOptions.PrivateKey DPoP 증명 서명에 사용하는 PEM 인코딩 개인 키.
DPoPOptions.PublicKeyJWK 공개 키의 JSON Web Key 표현.
GetDPoPProof() 특정 HTTP 메서드, URL, 액세스 토큰에 대한 DPoP 증명 JWT를 생성해요.
GetDPoPJWKThumbprint() 공개 키의 RFC 7638 SHA-256 썸프린트를 반환해요.
DPoPNonce 서버로부터 받은 현재 DPoP-Nonce 값(읽기 전용).

토큰 보안 강화하기

DPoP는 PKCE 이후 OAuth 2.0 토큰 보안에서 가장 중요한 개선 사항이에요. 최소한의 코드 변경으로 토큰 도용 공격 전체를 제거해요. DPoPOptions.Enabled := True로 설정하고 키를 제공하면 sgcWebSockets 컴포넌트가 나머지를 처리해요.

DPoP 지원은 Delphi(DXE6~D13)와 .NET(.NET Framework 2.0~.NET 9) 모두를 위한 sgcWebSockets 2026.4.0에서 사용할 수 있어요. esegece.com에서 최신 버전을 다운로드하세요.