PKCE (Proof Key for Code Exchange) extension to OAuth 2.0
図で分かりやすいと思います。
簡単な説明
クライアント側であらかじめcode_verifier
とcode_challenge
を作っておく。
OAuth2.0の認証API(/authorize
)にcode_challenge
を与える。
トークン交換API(/token
)でcode_verifier
を与える。
そうすると、認証サーバー側で 検証できて不正検知できる。
code_verifier
の作成
code_verifierはhigh-entropy cryptographic stringであることが期待。要は、セキュアな乱数関数を使って32bytesの長さのものを作ればよい。以下はおもちゃ的な実装だがイメージしやすい。
Swift実装
func generateCodeVerifier() -> String {
let length = 32
var randomBytes = [UInt8](repeating: 0, count: length)
let result = SecRandomCopyBytes(kSecRandomDefault, length, &randomBytes)
if result != errSecSuccess {
fatalError("生成できまちぇんでちた")
}
let codeVerifier = Data(randomBytes).base64EncodedString()
return codeVerifier
}
code_challenge
の作成
code_verifierから生成する。code_verifierをそのもの出してもいいらしいけどセキュリティ的に弱くなってしまうので、SHA256で生成するのは一般的。/authorize APIでcode_challenge_method=S256
渡したしね。
Swift実装
import Foundation
import CommonCrypto
func generateCodeChallenge(from codeVerifier: String) -> String? {
guard let data = codeVerifier.data(using: .utf8) else { return nil }
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes {
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
}
return Data(hash).base64EncodedString()
}
上記のシーケンス図
PlantUMLないとね
@startuml
actor User
participant "Client\nApp" as NativeClient
participant "WebView" as WebView
participant "Authorization\nServer" as AuthServer
participant "Resource\nServer" as ResourceServer
NativeClient -> NativeClient: Generate\n<font color=red>code_verifier=Random 32 bytes</font> and\n<font color=red>code_challenge=SHA256(code_verifier)</font>
NativeClient -> WebView++: Open WebView\nand pass challenge
WebView -> AuthServer: Authorization Request\n(GET /authorize?response_type=code&client_id=...&<font color=red>code_challenge=...&code_challenge_method=S256</font>&<font color=magenta>redirect_uri</font>=...)
AuthServer -> AuthServer: Store <font color=red>code_challenge</font> and\n<font color=red>code_challenge_method</font>
AuthServer --> WebView: 302 redirect to login form page
WebView --> User: Show login form
User -> WebView: Enter credentials\nand submit
WebView -> AuthServer: Send credentials
AuthServer --> WebView: Display consent screen
WebView --> User: Show consend screen
User -> WebView: Approve consent
WebView -> AuthServer: Approve consent
AuthServer -> WebView: Redirect to <font color=magenta>redirect_uri</font>\nwith authorization <font color=blue>code</font> (302 Redirect)
WebView -> NativeClient: Provide authorization <font color=blue>code</font>
deactivate WebView
NativeClient -> AuthServer: Access Token Request\n(POST /token with <font color=blue>code</font>, <font color=red>code_verifier</font>, <font color=magenta>redirect_uri</font>, ...)
AuthServer -> AuthServer: Verify <font color=red>code_verifier</font>\nmatches <font color=red>code_challenge</font>
AuthServer --> NativeClient: Access Token Response\n(including <b>access_token</b>, token_type, expires_in, refresh_token)
NativeClient -> ResourceServer: Request Resource\n(with <b>access_token</b>)
ResourceServer --> NativeClient: Resource Response
@enduml
以上です。
役にたつといいですね。