humanitec / django-oauth-toolkit-jwt

django-oauth-toolkit extension that adds JWT support
MIT License
38 stars 26 forks source link

Allow more robust JWT enrichment #29

Open com4 opened 3 years ago

com4 commented 3 years ago

I would like a way to make a more robust JWT modeling some of the claims after OIDC (though not completely compliant). To do this I need access to user information. Currently the token_enricher function is passed only the request object but it doesn't contain any user information since the user is not logged in.

I propose passing all known information to the token_enricher function and letting the user decide exactly what's included in the token. For example:

payload_enricher = getattr(settings, 'JWT_PAYLOAD_ENRICHER', None)
if payload_enricher:
    fn = import_string(payload_enricher)
    enriched_data = fn(
        request=request,
        token_content=content,
        token_obj=token,
        current_claims=extra_data)

    if getattr(settings, 'JWT_PAYLOAD_ENRICHER_OVERWRITE', False):
        extra_data = enriched_data
    else:
        extra_data.update(enriched_data)

This would allow for user functions that look more like this:

# myproject/myapp/jwt_utils.py

def payload_enricher(**kwargs):
    # Keyword Args: request, token_content, token_obj, current_claims

    # The Django HTTPRequest object
    request = kwargs.pop('request', None)

    # Dictionary of the content of the Oauth response. Includes values like
    # access_token, expires_in, token_type, refresh_token, scope
    content = kwargs.pop('token_content', None)

    # The oauth2_provider access token (by default:
    # oauth2_provider.models.AccessToken)
    token = kwargs.pop('token_obj', None)

    # The automatically generated claims. This usually includes your
    # JWT_ID_ATTRIBUTE and scope. This can be useful if you want to use
    # JWT_PAYLOAD_ENRICHER_OVERWRITE mode.
    current_claims = kwargs.pop('current_claims', None)

    # Values returned here must be serializable by json.dumps
    return {
        'sub': token.user.pk,
        'preferred_username': token.user.username,
        ...
    }

This implementation "works for me" but it may break backward compatibility with existing enrichment functions that accept a single named parameter (this should be fixed in the future by updating the documentation to accept **kwargs as in the above example).

I've opened a pull request (#30) if this is something others would find value in.