README.md
pkce 'Proof Key Code Exchange' or pronounced 'pixy' is a python PKCE library.
This library deals with the creation and verification of PKCE codes, ie: OAuth 2.0 'Code flow'
- Described here: Official PKCE Spec
- Here: oauth.net
- Here: auth0.com
- And here: xero.com
pip install git+https://github.com/xzava/pkce.git --upgrade
- No dependencies for the client server
- Only one optional import for the auth server.
pip install python-jose[cryptography]
>>> import pkce
>>> pkce.generate()
Pixy(
code_verifier='JEsUBbjgXB4szfBn7-LJ7vOir1t_rqBX8mLDHO-yeVdipl9PlS2gvRAPQsldb8MtkVZ_azGtqtQfn6dvRPPlgsWHDLr3HcLjEuuW9yq58Mgj7XW0lhwImt1smVdjF879',
code_challenge='C1MzkLRi_rKyRnxFkWa-5qfvuohwo5r3ufug4waI8Cw',
code_challenge_method='S256'
)
>>> pixy = pkce.generate()
>>> pixy.code_verifier #> a password..
'B98x18KCZsXdXoBKctzVnTmQ9_KaLQVSir6aL45zi1GuX_1MjBrfLb1DDAF4VBrRh4k2_-Fd9TTpSMWwYQki5P-bIfRoHsANtkqQofHe0xvut3SjQAzronvoIqlgftBl'
>>> pixy.code_challenge #> a hash of the password..
'UJFi4jeGi8t9IiYecJm7-1JWklXMDIKOaDHkYXqCw0k'
>>> pixy.code_challenge_method #> the hash method use to hash the password..
'S256'
>>> pkce.solve(pixy.code_verifier, pixy.code_challenge, pixy.code_challenge_method)
True
>>> pkce.solve(**dict(pkce.generate()))
True
>>> pkce.solve("password123", pixy.code_challenge, pixy.code_challenge_method)
{'error': 'invalid_request', 'error_description': 'verifier is out of spec'}
>>> pkce.solve("JEsUBbjgXB4szfBn7-LJ7vOir1t_rqBX8mLDHO-yeVdipl9PlS2gvRAPQsldb8MtkVZ_azGtqtQfn6dvRPPlgsWHDLr3HcLjEuuW9yq58Mgj7XW0lhwImt1smVdjF879", pixy.code_challenge, pixy.code_challenge_method)
{'error': 'invalid_grant', 'error_description': 'code verifier failed'}
>>> pkce.solve('B98x18KCZsXdXoBKctzVnTmQ9_KaLQVSir6aL45zi1GuX_1MjBrfLb1DDAF4VBrRh4k2_-Fd9TTpSMWwYQki5P-bIfRoHsANtkqQofHe0xvut3SjQAzronvoIqlgftBl', pixy.code_challenge, pixy.code_challenge_method)
True
- Client creates the pixy object,
- They save it in a database/keyvalue store, they need it later.
- Client sends
Code Challenge
to the server to say, 'I want aAuthorization Code
, remember me for later' - Server creates a
Authorization Code
and saves bothAuthorization Code
andCode Challenge
, they need it later. - Server sends the
Authorization Code
they created and theCode Challenge
from the client - Client uses the
Code Challenge
as a key to get theCode Verifier
that they saved somewhere - Client returns all three codes to the server.
Code Verifier
,Code Challenge
,Authorization Code
- Server checks the
Authorization Code
is unused and valid, then checks theCode Verifier
hashes into theCode Challenge
- Server says thanks I trust its you who made the request, here is the
Authorization Code
you requested.
-
Its possible for the server to encrypt all information inside the
Authorization Code
and pass that back to the client, avoid a database round trip. The sever should still check for expiry and token reuse, the latter still requires database. -
I have also seen JavaScript web apps store information encrypted in the headers, how you store this information is up to you.
-
I store it in a key/value database (dynamodb using pynamite)
# https://datatracker.ietf.org/doc/html/rfc7636
Protocol ..........................................................8
4.1. Client Creates a Code Verifier .............................8
4.2. Client Creates the Code Challenge ..........................8
4.3. Client Sends the Code Challenge with the
Authorization Request ......................................9
4.4. Server Returns the Code ....................................9
4.4.1. Error Response ......................................9
4.5. Client Sends the Authorization Code and the Code
Verifier to the Token Endpoint ............................10
4.6. Server Verifies code_verifier before Returning the Tokens
This specification adds additional parameters to the OAuth 2.0
Authorization and Access Token Requests, shown in abstract form in
Figure 2.
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.
Info
=========
code verifier (private)
A cryptographically random string that is used to correlate the
authorization request to the token request.
code challenge (public)
A challenge derived from the code verifier that is sent in the
authorization request, to be verified against later.
code challenge method
A method that was used to derive code challenge.
Base64url Encoding
Base64 encoding using the URL- and filename-safe character set
defined in Section 5 of [RFC4648], with all trailing '='
characters omitted (as permitted by Section 3.2 of [RFC4648]) and
without the inclusion of any line breaks, whitespace, or other
additional characters. (See Appendix A for notes on implementing
base64url encoding without padding.)
pkce.generate()
pkce.make_verifier()
pkce.make_challenge()
pkce.solve()
pkce.create_auth_code()
pkce.load_auth_code()
pkce.compare() #> Compare Authorization Code's
create_auth_code()
and load_auth_code()
encrypt and decrypt the PKCE information into the Authorization Code
requires a FERNET_KEY env --> from cryptography.fernet import Fernet;Fernet.generate_key().decode()
Feel free to use your own method to store this information, in stateless or statefull way.
# Used for creating `Authorization Code` or 'Nonce'
>>> pkce.make_code() #> 'RhHQthqhHC7D6uy29YMInnKzOck5Rg74s36lMZ4gplT'
>>> pkce.short_code() #> 'sPYPr1evEU0EpROcqCAKz4yiDB2EzVTa'
Consider donating if you find this as useful as I do.
Making this free and useful is the right thing to do.