jpadilla / django-rest-framework-jwt

JSON Web Token Authentication support for Django REST Framework
http://jpadilla.github.io/django-rest-framework-jwt/
MIT License
3.19k stars 648 forks source link

added setting JWT_PUBLIC_KEY_USING_HEADER_HANDLER #471

Open devsnd opened 5 years ago

devsnd commented 5 years ago

added setting JWT_PUBLIC_KEY_USING_HEADER_HANDLER to dynamically fetch public keys based on the header

when setting an import path to a callable into JWT_PUBLIC_KEY_USING_HEADER_HANDLER, it will be called with the JWT header as parameter. This allows to e.g. fetch dynamically changing certificates, like google does it for example.

devsnd commented 5 years ago

Dear DRF-JWT developers,

I made this small change that allows to get a public key based on the header of the JWT token. This allows for easy integration with e.g. firebase, as the public keys and certificates are rotated hourly.

Here is some example code of how you could use this:

import requests
from OpenSSL import crypto

def get_public_key_for_payload(unverified_payload):
    kid = unverified_payload['kid']
    certificates = requests.get('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com').json()
    certificate = certificates[kid]
    pubkey = certificate.get_pubkey()
    pem_pubkey = crypto.dump_publickey(crypto.FILETYPE_PEM, pubkey)
    return pem_pubkey

Then you could use this callback to get the right pubkey like this:

JWT_PUBLIC_KEY_USING_HEADER_HANDLER = 'my_package.get_public_key_for_payload'

Please note that you should cache the request based using the kid as cache key in production!

codecov[bot] commented 5 years ago

Codecov Report

:exclamation: No coverage uploaded for pull request base (master@0a0bd40). Click here to learn what that means. The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff            @@
##             master     #471   +/-   ##
=========================================
  Coverage          ?   90.34%           
=========================================
  Files             ?       12           
  Lines             ?      818           
  Branches          ?       29           
=========================================
  Hits              ?      739           
  Misses            ?       66           
  Partials          ?       13
Flag Coverage Δ
#codecov 90.34% <ø> (?)
#dj110 87.04% <ø> (?)
#dj111 87.04% <ø> (?)
#dj18 89.48% <ø> (?)
#dj19 89.48% <ø> (?)
#drf31 89.48% <ø> (?)
#drf32 89.48% <ø> (?)
#drf33 89.48% <ø> (?)
#drf34 90.34% <ø> (?)
#drf35 89.97% <ø> (?)
#drf36 89.97% <ø> (?)
#py27 90.34% <ø> (?)
#py34 89.97% <ø> (?)
#py35 87.04% <ø> (?)
#py36 87.04% <ø> (?)
Impacted Files Coverage Δ
rest_framework_jwt/settings.py 100% <ø> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more Δ = absolute <relative> (impact), ø = not affected, ? = missing data Powered by Codecov. Last update 0a0bd40...9ef21a3. Read the comment docs.

devsnd commented 5 years ago

I fixed the JWT_AUTH settings being clobbered by DRF when calling reload on the APISettings object. This means that @override_settings decorators now work properly.

By fixing the settings being clobbered by the original APISettings implementation of restframework, we can now listen to settings changes using the setting_changed signal.

This signal is used by the @override_settings decorator. This means that you can now perform tests that would for example override the JWT_VERIFY_EXPIRATION setting, allowing for testing without generating new tokens on the fly.

E.g. this works now:

@override_settings(JWT_AUTH=dict(settings.JWT_AUTH, JWT_VERIFY_EXPIRATION=False))
def test(self):
    assert can_auth_with_expired_token()