# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['djognito']

package_data = \
{'': ['*']}

install_requires = \
['Django>=3',
 'djangorestframework>=3.13.1,<4.0.0',
 'python-jose[cryptography]>=3.3.0,<4.0.0']

setup_kwargs = {
    'name': 'djognito',
    'version': '0.1.1',
    'description': 'Auth module for using AWS Cognito with DRF',
    'long_description': '# Djognito\n\nA DRF Authentication module for verifying JWT Token issued by AWS Cognito.\n\n- [Djognito](#djognito)\n  - [Problem Statement](#problem-statement)\n  - [My usecase](#my-usecase)\n  - [Solution̦̦](#solution̦̦)\n    - [TL;DR version](#tldr-version)\n  - [A detailed view](#a-detailed-view)\n  - [How to use this module](#how-to-use-this-module)\n    - [Common](#common)\n    - [Case 1: You only want to authenticate using JWT](#case-1-you-only-want-to-authenticate-using-jwt)\n    - [Case 2: You want to authenticate using JWT and attach attributes/permissions](#case-2-you-want-to-authenticate-using-jwt-and-attach-attributespermissions)\n  - [Appendix](#appendix)\n    - [Pre-requisites](#pre-requisites)\n      - [JWT](#jwt)\n      - [AWS Cognito](#aws-cognito)\n\n## Problem Statement\n\nI wasn\'t able to find a DRF Authentication Module which simply allows you to \n  * Verify a JWT\n  * Attach a `user` object to request if verification is successful\n    * **Assumption**: `user` object only requires `username` attribute to be instantiated. Other attributes can be later added using `attach_attributes` hook provided.\n\nEvery solution I came across does a lookup on `User` model in database, thus defeating the _statelessness_ of JWT.\n\n## My usecase\n\nI have engineered the frontend to send relevant tokens using `cookies`. I just wanted to\n\n*  Verify the  `JWT` stored in `${ACCESS_TOKEN_KEY}` cookie.\n*  Attach a `user` object to my `request` if verification succeeds.\n*  Avoid any DB lookups.\n*  Attach permissions to user depending on some of the JWT fields(`cognito:groups` in my case).\n\n\n## Solution̦̦\n\n### TL;DR version\n\nCreated a DRF Authentication Module which simply looks at the cookies and verifies the user. If user verification succeeds, \na `user` object is created and attached to `request` object. Rest of the code can use `request.user` to access the created user. \n\n## A detailed view\n\nThe 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. \n\n**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.\n\nThe following assignment operation happens\n```python\nfrom django.contrib.auth.models import User\nuser = User(username=username) # Username is created using username claim present in your JWT\n```\n\nThen, 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.\n\nNote that this module is designed to avoid DB lookups at all. \n\n## How to use this module\n\n### Common\n\nKindly ensure that following environment variables are set:\n\n```python\nACCESS_TOKEN_KEY= #  Points to the cookie key containing access token issued by AWS Cognito\nAWS_COGNITO_APP_CLIENT_ID= # Your Cognito App client ID\nAWS_COGNITO_REGION= # Region in which your Cognito Server exists\nAWS_COGNITO_USER_POOL_ID= # Your Cognito User pool ID \n```\n\n### Case 1: You only want to authenticate using JWT\n\nEnsure that environment variables described in [common](#common) sections are set\n\nAdd  `djognito.authentication.BaseCognitoAuthentication` to `DEFAULT_AUTHENTICATION_CLASSES` in `settings.py`. Finally, your `REST_FRAMEWORK` dict should look like\n\n```python\nREST_FRAMEWORK = {\n    ....\n    \'DEFAULT_AUTHENTICATION_CLASSES\': (\n        "djognito.authentication.BaseCognitoAuthentication",\n        ....\n    ),\n    ....\n}\n```\n\n### Case 2: You want to authenticate using JWT and attach attributes/permissions\n\nEnsure that environment variables described in [common](#common) sections are set\n\n1. Create a class inheriting `BaseCognitoAuthentication` and override the `attach_attributes` method.\n2. Add that class in your `DEFAULT_AUTHENTICATION_CLASSES` list.\n\nThe 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.\n\nThis example assumes that every user is part of at least one AWS Cognito group.\n\n```python\nfrom djognito.authentication import BaseCognitoAuthentication\nfrom djognito.jwt_utils import verify_jwt\nfrom rest_framework import authentication\nfrom rest_framework import exceptions\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\nclass CognitoAuthentication(BaseCognitoAuthentication):\n    def attach_attributes(self, user, request):\n        access_token = request.COOKIES.get(\'accessToken\', \'\')\n        claims = verify_jwt(access_token)\n        user.groups = claims[\'cognito:groups\']\n        if len(claims[\'cognito:groups\']) > 1:\n            logger.warning(\n                f\'User {claims["username"]} belongs to multiple group: {claims["cognito: groups"]}.\')\n```\n\nAssuming 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\n```python\nREST_FRAMEWORK = {\n    ....\n    \'DEFAULT_AUTHENTICATION_CLASSES\': (\n        "authentication.CognitoAuthentication",\n        ....\n    ),\n    ....\n}\n```\n\nYou can read more about the flow of control [here](#a-detailed-view)\n\n## Appendix\n\n### Pre-requisites\n\n#### JWT\n\nOne 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:\n\n* Performance boost: database lookup may significantly increase your latency\n* Separation of Resources: The whole application can be divided into two distinct set of resources: `AuthServer` and `ResourceServer`. This has following benefits: \n  * One can have a separate team/workforce to ensure that `Auth` server meets standard compliances (`HIPAA`, `FedRAMP` etc).\n  * `ResourcesServer` and `AuthServer` can scale independently.\n\n#### AWS Cognito\n\nAWS 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](https://aws.amazon.com/cognito/)\n',
    'author': 'Prakash2403',
    'author_email': 'rishurai24@gmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/Prakash2403/Djognito',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>3.6',
}


setup(**setup_kwargs)
