Proof Key for Code Exchange
PKCE(Proof Key for Code Exchange)란? 동일한 컴퓨터에 침입하는 악성 프로그램이 인증 코드를 가로채는 것을 방지하기 위한 모바일 장치의 공용 클라이언트용 OAuth 2.0 보안 확장이다. (loginradius)
About
PKCE는 간단히 말해서 Authorization Code Grant 방식에서 Client Secret을 이용하던 것을 일회용 암호를 이용하는 것으로 변경한 것입니다.
PKCE(Proof Key for Code Exchange)로 알려진 인증을 받기 위한 코드를 교환할 때 증명할 수 있는 키를 사용하는 방식은 악의적인 공격을 방지하고 Authorization Code Grant 방식을 안전하게 수행하기 위한 방법입니다.
주로 Client Secret을 사용하는 클라이언트 애플리케이션이나 Authorization Code Grant 방식에 사용되는 Client Secret을 대체하기에 유용합니다.
이 방식은 기본적으로 code_verifier, code_challenge, code_challenge_method 라는 세 개의 매개 변수와 함께 동작합니다.
말로 설명하는 것보다 사진을 보는 것이 더 이해가 잘 될 것입니다. 아래 사진에 Authorization Code Grant + PKCE 방식이 잘 나타나있습니다.
Examples
generatePKCEVerifier and generatePKCEChallenge
function dec2hex(dec: number) {
return ('0' + dec.toString(16)).substr(-2)
}
// Functions below taken from: https://stackoverflow.com/questions/63309409/creating-a-code-verifier-and-challenge-for-pkce-auth-on-spotify-api-in-reactjs
export function generatePKCEVerifier() {
const verifierLength = 56
const array = new Uint32Array(verifierLength)
if (typeof crypto === 'undefined') {
const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'
const charSetLen = charSet.length
let verifier = ''
for (let i = 0; i < verifierLength; i++) {
verifier += charSet.charAt(Math.floor(Math.random() * charSetLen))
}
return verifier
}
crypto.getRandomValues(array)
return Array.from(array, dec2hex).join('')
}
async function sha256(randomString: string) {
const encoder = new TextEncoder()
const encodedData = encoder.encode(randomString)
const hash = await crypto.subtle.digest('SHA-256', encodedData)
const bytes = new Uint8Array(hash)
return Array.from(bytes)
.map((c) => String.fromCharCode(c))
.join('')
}
function base64urlencode(str: string) {
return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}
export async function generatePKCEChallenge(verifier: string) {
const hasCryptoSupport =
typeof crypto !== 'undefined' &&
typeof crypto.subtle !== 'undefined' &&
typeof TextEncoder !== 'undefined'
if (!hasCryptoSupport) {
console.warn(
'WebCrypto API is not supported. Code challenge method will default to use plain instead of sha256.'
)
return verifier
}
const hashed = await sha256(verifier)
return base64urlencode(hashed)
}