This package contains an authentication mechanism for authenticating users of a REST API using tokens obtained from OpenID Connect.
Currently, it only supports JWT and Bearer tokens. JWT tokens will be validated against the public keys of an OpenID connect authorization service. Bearer tokens are used to retrieve the OpenID UserInfo for a user to identify him.
Install using pip:
pip install drf-oidc-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.',
}
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',
)
pip install tox
tox
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)
- Requires Django REST Framework
- And of course Django
- Inspired on REST framework JWT Auth