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

New in version v0.10.

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. If you are using Flask, check the section Authorization Code Grant.

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 create_authorization_code method:

class MyAuthorizationCodeGrant(AuthorizationCodeGrant):
    def create_authorization_code(self, client, grant_user, request):
        code = generate_token(48)

        # NOTICE BELOW
        code_challenge = request.data.get('code_challenge')
        code_challenge_method = request.data.get('code_challenge_method')
        item = AuthorizationCode(
            code=code,
            client_id=client.client_id,
            redirect_uri=request.redirect_uri,
            scope=request.scope,
            user_id=grant_user.get_user_id(),
            code_challenge=code_challenge,
            code_challenge_method=code_challenge_method,
        )
        db.session.add(item)
        db.session.commit()
        return 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. 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 framework integrations:

  1. Flask OAuth Client.
  2. Django OAuth Client.

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-a-urlsafe-string'
>>> code_challenge = create_s256_code_challenge(code_verifier)
>>> uri, state = session.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_access_token(access_token_url, authorization_response=authorization_response, code_verifier=code_verifier)

The authorization flow is the same as in OAuth 2 Session, what you need to do is:

  1. adding code_challenge and code_challenge_method in authorization_url().
  2. adding code_verifier in fetch_access_token().

API Reference

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

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 create_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 CAN 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 CAN 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