django-oauth-toolkit icon indicating copy to clipboard operation
django-oauth-toolkit copied to clipboard

private_key_jwt support?

Open LiteWait opened this issue 2 years ago • 2 comments

Plan on using client_credentials grant type, but I don't see where private_key_jwt supported. Pretty critical to maintain good security with server to server implementations. Please advise. Thanks.

LiteWait avatar Mar 16 '23 21:03 LiteWait

If you are toking about jwt-bearer as client assertion type you can use the following code using extended oauth2 validator. Standard: https://www.rfc-editor.org/rfc/rfc7523#section-2.2

I haven't prepared a PR but you can use this code:

public_key is defined in a extended application model:

class MyApplication(oauth2_provider_models.AbstractApplication):
  public_key = models.TextField('Public key (Jwt Grant)', blank=True, null=True)
JWT_BEARER_CLIENT_ASSERTION_TYPE = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'

class MyOauth2Validator(oauth2_validators.OAuth2Validator):
    def _authenticate_jwt_bearer(self, request: common.Request):
        try:
            client_id = request.client_id
            decoded_body = dict(request.decoded_body or [])
            client_assertion_type = decoded_body['client_assertion_type']
            client_assertion = decoded_body['client_assertion']
        except (AttributeError, KeyError):
            return False

        if client_assertion_type != JWT_BEARER_CLIENT_ASSERTION_TYPE:
            return False

        if self._load_application(client_id, request) is None:
            log.debug('Failed basic auth: Application %s does not exist' % client_id)  # noqa: S001
            return False

        public_key = _get_public_key(request.client.public_key)
        if public_key is None:
            return False

        try:
            assertion = jwt.decode(
                client_assertion,
                key=public_key,
                algorithms=['RS256', 'RS384', 'RS512'],
                audience='https://www.bitstamp.net',
            )

            if client_id != assertion.get('iss') or client_id != assertion.get('sub'):
                return False

            return True
        except jwt.PyJWTError as e:
            log.info('Client error in token decode. %s.', e)
            return False

    def authenticate_client(self, request, *args, **kwargs):
        authenticated = super().authenticate_client(request, *args, **kwargs)
        if not authenticated:
            authenticated = self._authenticate_jwt_bearer(request)
        return authenticated


def _get_public_key(key):
    # type: (str) -> typing.Any
    if key is not None and key.startswith('-----BEGIN CERTIFICATE-----'):
        key = x509.load_pem_x509_certificate(key.encode('utf-8'))
        return key.public_key()

    return key

matejsp avatar Mar 28 '23 07:03 matejsp

@LiteWait what do you mean by private_key_jwt support? can you link to defining OAuth or OIDC specification?

dopry avatar Oct 04 '23 14:10 dopry