Skip to main content

Package for authenticating JWT tokens from multiple issuers

Project description

JWT multi-issuer authentication for Django Rest Framework

This package is a fork of drf-oidc-auth with added functionality of supporting multiple JWT issuers and a few other fixes.

Currently, it only supports JWT and Bearer tokens. JWT tokens will be validated against the public keys of various sources that can be configured. These can either be JWKS endpoints or PEM strings.

Bearer tokens are used to retrieve OpenID UserInfo for a user to identify him. These tokens are not a priority for this fork, so these only support one OpenID endpoint.

Installation

Install using pip:

pip install drf-jwt-multi-auth

Configure authentication for Django REST Framework in settings.py:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        # ...
        'oidc_auth.authentication.JSONWebTokenAuthentication',
        'oidc_auth.authentication.BearerTokenAuthentication',
    ),
    'UNAUTHENTICATED_USER': None,
}

These can also be set manually for the API view, it does not have to be registered as the default authentication classes.

And configure the module itself in settings.py:

OIDC_AUTH = {
    # Define multiple issuers in here, each with
    # an `type`, `key` and `claims_options` value.
    # The key for each issuer in the dict will be the expected value for
    # the 'iss' claim in tokens from that issuer.
    # `claims_options` can now be defined according to this documentation:
    # ref: https://docs.authlib.org/en/latest/jose/jwt.html#jwt-payload-claims-validation
    # `type` can be "PEM" or "JWKS". If "PEM", then `key` must be a public key
    # in PEM format. if "JWKS`, then `key` must be a JWKS endpoint
    # `aud` is only required, when you set it as an essential claim.
    'JWT_ISSUERS': {
        'https://google.com': {
            'type': 'JWKS',
            'key': 'https://accounts.google.com',
            'claims_options': {
                'aud': {
                    'values': ['myapp']
                    'essential': True,
                }
            },
        }
    }

    # (Optional) Function that resolves id_token into user.
    # This function receives a request and an id_token dict and expects to
    # return a User object. The default implementation returns None.
    # See  the User authentication section for more info.
    'OIDC_RESOLVE_USER_FUNCTION': 'oidc_auth.authentication.get_user_none',

    # (Optional) Time before signing keys will be refreshed (default 24 hrs)
    'JWKS_EXPIRATION_TIME': 24*60*60,

    # (Optional) Time before bearer token validity is verified again (default 10 minutes)
    'OIDC_BEARER_TOKEN_EXPIRATION_TIME': 10*60,

    # (Optional) Token prefix in JWT authorization header (default 'JWT')
    'JWT_AUTH_HEADER_PREFIX': 'JWT',

    # (Optional) Token prefix in Bearer authorization header (default 'Bearer')
    'BEARER_AUTH_HEADER_PREFIX': 'Bearer',

    # (Optional) Which Django cache to use
    'OIDC_CACHE_NAME': 'default',

    # (Optional) A cache key prefix when storing and retrieving cached values
    'OIDC_CACHE_PREFIX': 'oidc_auth.',

    # (Optional) Leeway gives some +- leeway to when a token is active. Given in seconds
    # This helps account for clockskew (default 5 seconds)
    'LEEWAY': 5,
}

User authentication

By default, this plugin does not authenticate a user. As long as the token itself is validated succesfully, it will be a success. This will cause problems if your permission classes require a user to be authenticated, or your API in general requires a User to be authenticated. In order to authenticate a user, a custom function can be defined in the OIDC_RESOLVE_USER_FUNCTION setting. An example can look like this:

from django.contrib.auth import get_user_model
from rest_framework.exceptions import AuthenticationFailed

def get_user_by_id(request, id_token)
    User = get_user_model()
    try:
        user = User.objects.get(username=id_token.get('sub'))
    except User.DoesNotExist:
        msg = _('Invalid Authorization header. User not found.')
        raise AuthenticationFailed(msg)
    return user

This will authenticate as the user with a username matching the sub claim in the token. If no such user exists, the authentication fails. Using the Django user models will require the django.contrib.auth anddjango.contrib.contenttypes apps to be configured in the django settings.py file like so:

INSTALLED_APPS = (
    #  ...
    'django.contrib.auth',
    'django.contrib.contenttypes',
)

Running tests

pip install tox
tox

Mocking authentication

There's a AuthenticationTestCaseMixin provided in the oidc_auth.test module, which you can use for testing authentication like so:

from oidc_auth.test import AuthenticationTestCaseMixin
from django.test import TestCase

class MyTestCase(AuthenticationTestCaseMixin, TestCase):
    def test_example_cache_of_valid_bearer_token(self):
        self.responder.set_response(
            'http://example.com/userinfo', {'sub': self.username})
        auth = 'Bearer egergerg'
        resp = self.client.get('/test/', HTTP_AUTHORIZATION=auth)
        self.assertEqual(resp.status_code, 200)

        # Token expires, but validity is cached
        self.responder.set_response('http://example.com/userinfo', "", 401)
        resp = self.client.get('/test/', HTTP_AUTHORIZATION=auth)
        self.assertEqual(resp.status_code, 200)

    def test_example_using_invalid_bearer_token(self):
        self.responder.set_response('http://example.com/userinfo', "", 401)
        auth = 'Bearer hjikasdf'
        resp = self.client.get('/test/', HTTP_AUTHORIZATION=auth)
        self.assertEqual(resp.status_code, 401)

References

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

drf_jwt_multi_auth-1.1.0.tar.gz (20.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

drf_jwt_multi_auth-1.1.0-py3-none-any.whl (9.7 kB view details)

Uploaded Python 3

File details

Details for the file drf_jwt_multi_auth-1.1.0.tar.gz.

File metadata

  • Download URL: drf_jwt_multi_auth-1.1.0.tar.gz
  • Upload date:
  • Size: 20.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.14

File hashes

Hashes for drf_jwt_multi_auth-1.1.0.tar.gz
Algorithm Hash digest
SHA256 00855780f246e65b8266d23ba4657f6e12ccdae0811af0a65348973b308d89fe
MD5 811afb064e32d210b89cb3d6bbd22b37
BLAKE2b-256 ee71ad7d7a4350c9eb30b7d1ba510e9ed0d3de640599d045b42db307de6e67f6

See more details on using hashes here.

File details

Details for the file drf_jwt_multi_auth-1.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for drf_jwt_multi_auth-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 03ae20015db41905a4ca22df312a5237944861915e5482c7e8a5d41f2b058443
MD5 5ca88dd3bbf4670ad46f1f87ef3cbf36
BLAKE2b-256 b45279cbeaffdaef2c3b8f2fe63d567bf7e430f3d8ebbb6c3a4680e225c1b6a6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page