RFC7636: Proof Key for Code Exchange by OAuth Public Clients

This RFC7636 is used to improve the security of Authorization Code flow for public clients by sending extra “code_challenge” and “code_verifier” to the authorization server.

Using RFC7636 in Authorization Code Grant

In order to apply proof key for code exchange, you need to register the CodeChallenge extension to AuthorizationCodeGrant. But before that, we need to re-design our AuthorizationCode database.

The new database SHOULD contain two more columns:

  1. code_challenge: A VARCHAR

  2. code_challenge_method: A VARCHAR

And the AuthorizationCodeGrant should record the code_challenge and code_challenge_method into database in save_authorization_code method:

class MyAuthorizationCodeGrant(AuthorizationCodeGrant):
    # YOU MAY NEED TO ADD "none" METHOD FOR AUTHORIZATION WITHOUT CLIENT SECRET
    TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_basic', 'client_secret_post', 'none']

    def save_authorization_code(self, code, request):
        # NOTICE BELOW
        code_challenge = request.data.get('code_challenge')
        code_challenge_method = request.data.get('code_challenge_method')
        auth_code = AuthorizationCode(
            code=code,
            client_id=request.client.client_id,
            redirect_uri=request.redirect_uri,
            scope=request.scope,
            user_id=request.user.id,
            code_challenge=code_challenge,
            code_challenge_method=code_challenge_method,
        )
        auth_code.save()
        return auth_code

Now you can register your AuthorizationCodeGrant with the extension:

from authlib.oauth2.rfc7636 import CodeChallenge
server.register_grant(MyAuthorizationCodeGrant, [CodeChallenge(required=True)])

If required=True, code challenge is required for authorization code flow from public clients. If required=False, it is optional, it will only valid the code challenge when clients send these parameters.

Using code_challenge in Client

Read the Code Challenge section in the Web OAuth Clients.

It is also possible to add code_challenge in OAuth2Session, consider that we already have a session:

>>> from authlib.oauth2.rfc7636 import create_s256_code_challenge
>>> code_verifier = generate_token(48)
>>> code_challenge = create_s256_code_challenge(code_verifier)
>>> uri, state = session.create_authorization_url(authorize_url, code_challenge=code_challenge, code_challenge_method='S256')
>>> # visit uri, get the response
>>> authorization_response = 'https://example.com/auth?code=42..e9&state=d..t'
>>> token = session.fetch_token(token_endpoint, authorization_response=authorization_response, code_verifier=code_verifier)

API Reference

class authlib.oauth2.rfc7636.CodeChallenge(required=True)

CodeChallenge extension to Authorization Code Grant. It is used to improve the security of Authorization Code flow for public clients by sending extra “code_challenge” and “code_verifier” to the authorization server.

The AuthorizationCodeGrant SHOULD save the code_challenge and code_challenge_method into database when save_authorization_code. Then register this extension via:

server.register_grant(
    AuthorizationCodeGrant,
    [CodeChallenge(required=True)]
)
DEFAULT_CODE_CHALLENGE_METHOD = 'plain'

defaults to “plain” if not present in the request

SUPPORTED_CODE_CHALLENGE_METHOD = ['plain', 'S256']

supported code_challenge_method

get_authorization_code_challenge(authorization_code)

Get “code_challenge” associated with this authorization code. Developers MAY re-implement it in subclass, the default logic:

def get_authorization_code_challenge(self, authorization_code):
    return authorization_code.code_challenge
Parameters:

authorization_code – the instance of authorization_code

get_authorization_code_challenge_method(authorization_code)

Get “code_challenge_method” associated with this authorization code. Developers MAY re-implement it in subclass, the default logic:

def get_authorization_code_challenge_method(self, authorization_code):
    return authorization_code.code_challenge_method
Parameters:

authorization_code – the instance of authorization_code