Skip to main content

Auth module for using AWS Cognito with DRF

Project description

Djognito

A DRF Authentication module for verifying JWT Token issued by AWS Cognito.

Problem Statement

I wasn't able to find a DRF Authentication Module which simply allows you to

  • Verify a JWT
  • Attach a user object to request if verification is successful
    • Assumption: user object only requires username attribute to be instantiated. Other attributes can be later added using attach_attributes hook provided.

Every solution I came across does a lookup on User model in database, thus defeating the statelessness of JWT.

My usecase

I have engineered the frontend to send relevant tokens using cookies. I just wanted to

  • Verify the JWT stored in ${ACCESS_TOKEN_KEY} cookie.
  • Attach a user object to my request if verification succeeds.
  • Avoid any DB lookups.
  • Attach permissions to user depending on some of the JWT fields(cognito:groups in my case).

Solution̦̦

TL;DR version

Created a DRF Authentication Module which simply looks at the cookies and verifies the user. If user verification succeeds, a user object is created and attached to request object. Rest of the code can use request.user to access the created user.

A detailed view

The authentication module picks up the JWT stored in cookie named ${ACCESS_TOKEN_KEY}, verifies it, and creates a user if the verification succeeds. It only uses username to instantiate the user object.

Note: It is expected that username is present in your JWT as a claim. If you're using AWS Cognito, then your JWT will contain a username attribute.

The following assignment operation happens

from django.contrib.auth.models import User
user = User(username=username) # Username is created using username claim present in your JWT

Then, the created user object and the request is passed to attach_attribute hook, which attaches necessary permissions (or attributes) to your user object. The user object is modified in-place. Finally, if everything succeeds, the user object is returned by the authenticate method, which in-turn attaches it to request object. Now, you can use request.user object anywhere in your downstream application.

Note that this module is designed to avoid DB lookups at all.

How to use this module

Common

Kindly ensure that following environment variables are set:

ACCESS_TOKEN_KEY= #  Points to the cookie key containing access token issued by AWS Cognito
AWS_COGNITO_APP_CLIENT_ID= # Your Cognito App client ID
AWS_COGNITO_REGION= # Region in which your Cognito Server exists
AWS_COGNITO_USER_POOL_ID= # Your Cognito User pool ID 

Case 1: You only want to authenticate using JWT

Ensure that environment variables described in common sections are set

Add djognito.authentication.BaseCognitoAuthentication to DEFAULT_AUTHENTICATION_CLASSES in settings.py. Finally, your REST_FRAMEWORK dict should look like

REST_FRAMEWORK = {
    ....
    'DEFAULT_AUTHENTICATION_CLASSES': (
        "djognito.authentication.BaseCognitoAuthentication",
        ....
    ),
    ....
}

Case 2: You want to authenticate using JWT and attach attributes/permissions

Ensure that environment variables described in common sections are set

  1. Create a class inheriting BaseCognitoAuthentication and override the attach_attributes method.
  2. Add that class in your DEFAULT_AUTHENTICATION_CLASSES list.

The following example attaches an attribute called groups to user object. The downstream code can use request.user.groups anywhere to access the groups to which user belongs.

This example assumes that every user is part of at least one AWS Cognito group.

from djognito.authentication import BaseCognitoAuthentication
from djognito.jwt_utils import verify_jwt
from rest_framework import authentication
from rest_framework import exceptions
import logging

logger = logging.getLogger(__name__)


class CognitoAuthentication(BaseCognitoAuthentication):
    def attach_attributes(self, user, request):
        access_token = request.COOKIES.get('accessToken', '')
        claims = verify_jwt(access_token)
        user.groups = claims['cognito:groups']
        if len(claims['cognito:groups']) > 1:
            logger.warning(
                f'User {claims["username"]} belongs to multiple group: {claims["cognito: groups"]}.')

Assuming that the filename is authentication.py and PYTHONPATH is able to locate it, add authentication.CognitoAuthentication to your DEFAULT_AUTHENTICATION_CLASSES. Finally, your REST_FRAMEWORK dict should look like

REST_FRAMEWORK = {
    ....
    'DEFAULT_AUTHENTICATION_CLASSES': (
        "authentication.CognitoAuthentication",
        ....
    ),
    ....
}

You can read more about the flow of control here

Appendix

Pre-requisites

JWT

One of the primary usecase of JWT is stateless authentication. It allows you to assert the authenticity of a given user without performing a database lookup. There are two major advantages of this:

  • Performance boost: database lookup may significantly increase your latency
  • Separation of Resources: The whole application can be divided into two distinct set of resources: AuthServer and ResourceServer. This has following benefits:
    • One can have a separate team/workforce to ensure that Auth server meets standard compliances (HIPAA, FedRAMP etc).
    • ResourcesServer and AuthServer can scale independently.

AWS Cognito

AWS Cognito can act as a AuthServer for systems relying on Stateless Authentication. It uses JWT standard for issuing tokens and takes care of user management (sign-up, sign-in, account verification, MFA, etc). You can read more about AWS Cognito here

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

Djognito-0.1.1.tar.gz (6.5 kB view details)

Uploaded Source

Built Distribution

Djognito-0.1.1-py3-none-any.whl (7.4 kB view details)

Uploaded Python 3

File details

Details for the file Djognito-0.1.1.tar.gz.

File metadata

  • Download URL: Djognito-0.1.1.tar.gz
  • Upload date:
  • Size: 6.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.1.12 CPython/3.9.9 Darwin/21.2.0

File hashes

Hashes for Djognito-0.1.1.tar.gz
Algorithm Hash digest
SHA256 08c681f1bc703b41f0934e0e2aa03967aa324c1b3aa95c7b561b2e983df298b3
MD5 12b82b6eb15d891f8b06cc30385b8f49
BLAKE2b-256 c19475410d30d6c6f7534cf0c1a357b318be33b833cd31a45e9162d0149858c4

See more details on using hashes here.

File details

Details for the file Djognito-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: Djognito-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 7.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.1.12 CPython/3.9.9 Darwin/21.2.0

File hashes

Hashes for Djognito-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 169ee46da83e7c1fe3ee67894ecf9cefad244ddce24ad8ec44dcf0b7ed98d7e7
MD5 6e42d6b32e63ac0e22b1feb2d60b4bd5
BLAKE2b-256 4bba4c4e8a093e3be945a4feb92373ee47622962dbb434732ef3d88fb15760c7

See more details on using hashes here.

Supported by

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