Source code for latch_cli.auth.pkce

import hashlib
import secrets
from typing import Tuple

from latch_cli.auth.utils import _base64url_encode


[docs]class PKCE: """A Context manager to control state for PKCE flow. The Proof Key for Code Exchange is outlined rigorously in `RFC7636`_. The implementation in this module was written using that document as gospel. It is an extension of OAuth2.0 that adds crytographic secrets to prevent request interception. Here is a diagram to summarize the flow (capital letters correspond to descriptions): :: +-------------------+ | Authz Server | +--------+ | +---------------+ | | |--(A)- Authorization Request ---->| | | | | + t(code_verifier), t_m | | Authorization | | | | | | Endpoint | | | |<-(B)---- Authorization Code -----| | | | | | +---------------+ | | Client | | | | | | +---------------+ | | |--(C)-- Access Token Request ---->| | | | | + code_verifier | | Token | | | | | | Endpoint | | | |<-(D)------ Access Token ---------| | | +--------+ | +---------------+ | +-------------------+ Figure 2: Abstract Protocol Flow A. The client creates and records a secret named the "code_verifier" and derives a transformed version "t(code_verifier)" (referred to as the "code_challenge"), which is sent in the OAuth 2.0 Authorization Request along with the transformation method "t_m". B. The Authorization Endpoint responds as usual but records "t(code_verifier)" and the transformation method. C. The client then sends the authorization code in the Access Token Request as usual but includes the "code_verifier" secret generated at (A). D. The authorization server transforms "code_verifier" and compares it to "t(code_verifier)" from (B). Access is denied if they are not equal. An attacker who intercepts the authorization code at (B) is unable to redeem it for an access token, as they are not in possession of the "code_verifier" secret. Example usage: :: with PKCE() as pkce: oauth2_flow = OAuth2(pkce, ..) .. _RFC7636: https://datatracker.ietf.org/doc/html/rfc7636 """ challenge_method = "S256" """The challenge method used to encode the code verifier. 'If the client is capable of using "S256", it MUST use "S256", as "S256" is Mandatory To Implement (MTI) on the server.' .. _RFC7636#section-4.2: https://datatracker.ietf.org/doc/html/rfc7636#section-4.2 Thus this value is hardcoded. """ def __init__(self): self.verifier, self.challenge = self.construct_challenge() def __enter__(self, *args): return self def __exit__(self, *args): ...
[docs] def construct_challenge(self) -> Tuple[str, str]: """Construct verifier & challenge to verify a client's identity in PKCE. Reference `RFC7636`_. Returns: code verifier: A cryptographically random string that is used to correlate the authorization request to the token request. code challenge: A challenge derived from the code verifier that is sent in the authorization request, to be verified against later. .. _RFC7636: https://datatracker.ietf.org/doc/html/rfc7636 """ verifier = _base64url_encode(secrets.token_bytes(32)) challenge = _base64url_encode(hashlib.sha256(verifier.encode()).digest()) return verifier, challenge