{"id":25043677,"url":"https://github.com/husein14azimi/jwt","last_synced_at":"2025-03-30T23:25:40.996Z","repository":{"id":264575585,"uuid":"890810722","full_name":"husein14azimi/jwt","owner":"husein14azimi","description":"jwt auth template project","archived":false,"fork":false,"pushed_at":"2025-01-10T08:26:43.000Z","size":69,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-06T04:58:04.266Z","etag":null,"topics":["django-rest-framework","djoser","jwt"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/husein14azimi.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-19T08:14:52.000Z","updated_at":"2025-01-10T08:26:46.000Z","dependencies_parsed_at":null,"dependency_job_id":"bea51b04-5df7-4959-9b71-77cbc69f61fe","html_url":"https://github.com/husein14azimi/jwt","commit_stats":null,"previous_names":["husein14azimi/jwt"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/husein14azimi%2Fjwt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/husein14azimi%2Fjwt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/husein14azimi%2Fjwt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/husein14azimi%2Fjwt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/husein14azimi","download_url":"https://codeload.github.com/husein14azimi/jwt/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246393867,"owners_count":20770027,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["django-rest-framework","djoser","jwt"],"created_at":"2025-02-06T04:58:07.324Z","updated_at":"2025-03-30T23:25:40.973Z","avatar_url":"https://github.com/husein14azimi.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cbr\u003e\u003c/br\u003e\n\u003cp align=\"center\"\u003e\nIN THE NAME OF GOD\n\u003c/p\u003e\n\u003cbr\u003e\u003c/br\u003e\n\n# jwt project 💻\n\nthis is a simple django project with two web applications:\n1. main app (core)\n2. profile app (account)\n\n\u003e it is actually a template project for authenticating via jwt\n\n### technologies used in the project:\n* RESTful apis\n* (Authentication): `jwt` (as the authenticatoin backend) and `djoser` (for pre-written `views` and `urls`)\n* (dev environment:) Visual Studio Code on Windows\n\n### TO-DO list for the future:\n* create-new-user api endpoint\n* email validation implementation\n\n\n\n\u003e [!IMPORTANT]\n\u003e In this documentation, it is assumed that you are a django amateur or above. If you are a beginner, you can use AI chatbots to guide you through these steps.\n\n\n\n# creating this project, step by step:\n\ncreate a project named `core` and then rename the project to whatever you like. renaming the project is easier than renaming the main application of the project.\n\n**note:** in this doc, in each file's code snippet, the full content of the file is typed; but in some cases (such as `settings.py`), only the code that should be modified is there.\n\n#### editing the `core.settings`\n\nregister `core` as a web app in this project:\n```\n# core.settings\n\nINSTALLED_APPS = [\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n    'core',\n]\n```\nif you're working on local host, write:\n```\nALLOWED_HOSTS = ['127.0.0.1', 'localhost',]\n```\n\nyou can also edit the time zone:\n```\nTIME_ZONE = 'Asia/Tehran'\n```\n\n\n### virtual environment\nvirtual environment is dependent on the address of its directory; that's why it is created after renaming the project. change `\u003cthe_name\u003e` to your desired name:\n\n```\npython -m venv \u003cthe_name\u003e\n```\n\ninstall django for your venv\n```\npip install django\n```\n\n## the `user` model\n\nextend the abstract user in the `core.models` so extra fields based on the project requirements can be added to the django base `User` model.\n\nthe `username` in this project has no place and the field required for login is `email`. to achieve that, the `username` field is set to `blank=True`. so far, `core.User` does not require any `username` field; but there is some code in the default django codes that still require `username` (which is the `UserManager`). we will change it and use it in the `core.User`.\n\n\n```\n# core.models\n\nfrom django.db import models\nfrom django.contrib.auth.models import AbstractUser  as BaseAbstractUser \nfrom django.contrib.auth.models import BaseUserManager\nfrom django.core.validators import RegexValidator\n\n\nclass UserManager(BaseUserManager):\n    def create_user(self, email, phone_number, password=None, **extra_fields):\n        \"\"\"Create and return a 'User' with an email, phone number and password.\"\"\"\n        if not email:\n            raise ValueError('The Email field must be set')\n        if not phone_number:\n            raise ValueError('The Phone number field must be set')\n        \n        email = self.normalize_email(email)\n        user = self.model(email=email, phone_number=phone_number, **extra_fields)\n        user.set_password(password)  # Use set_password to hash the password\n        user.save(using=self._db)\n        return user\n\n    def create_superuser(self, email, phone_number, password=None, **extra_fields):\n        \"\"\"Create and return a superuser with an email, phone number and password.\"\"\"\n        extra_fields.setdefault('is_staff', True)\n        extra_fields.setdefault('is_superuser', True)\n\n        if extra_fields.get('is_staff') is not True:\n            raise ValueError('Superuser must have is_staff=True.')\n        if extra_fields.get('is_superuser') is not True:\n            raise ValueError('Superuser must have is_superuser=True.')\n\n        return self.create_user(email, phone_number, password, **extra_fields)\n    \n\nclass User(BaseAbstractUser ):\n    username = models.CharField(max_length=255, unique=False, blank=True, null=True)\n    email = models.EmailField(unique=True)\n    phone_regex = RegexValidator(regex=r'^09\\d{9}$', message=\"Phone number must start with 09 and be exactly 11 characters.\")\n    phone_number = models.CharField(validators=[phone_regex], max_length=11, unique=True)\n\n    USERNAME_FIELD = 'email'\n    \n    REQUIRED_FIELDS = ['phone_number']\n\n    objects = UserManager()\n\n    def __str__(self):\n        return f'{self.first_name} {self.last_name}: {self.email}'\n```\n\n\n### admin-registering\n\nwe also register this specific `User` to the admin panel. since there are some modified fields in it, they have to be shown to django:\n\n```\n# core.admin\n\nfrom django.contrib import admin\nfrom django.contrib.auth.admin import UserAdmin as BaseUserAdmin\nfrom .models import User\n\nclass UserAdmin(BaseUserAdmin):\n    model = User\n    list_display = ('email', 'first_name', 'last_name', 'is_staff')\n    list_filter = ('is_staff', 'is_active')\n    ordering = ('email',)\n    fieldsets = (\n        (None, {'fields': ('email', 'password')}),\n        ('Personal info', {'fields': ('first_name', 'last_name', 'phone_number')}),\n        ('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}),\n        ('Important dates', {'fields': ('last_login',)}),\n    )\n    add_fieldsets = (\n        (None, {\n            'classes': ('wide',),\n            'fields': ('email', 'password1', 'password2', 'phone_number', 'is_staff', 'is_active')}\n        ),\n    )\n    search_fields = ('email',)\n    filter_horizontal = ('groups', 'user_permissions',)\n\nadmin.site.register(User, UserAdmin)\n```\n\n\n\nadd to the `core.settings`:\n```\n# core.settings\n\nAUTH_USER_MODEL= 'core.User'\n```\n\n## migrating\n\nmaybe in the first migration, the `core` app is not recognized; therefore, it is recommended to run:\n```\npython manage.py makemigrations core\n```\n\nand then run the global migration:\n```\npython manage.py makemigrations\n```\n```\npython manage.py migrate\n```\n\n#### creating a superuser\n\n\u003e [!CAUTION]\n\u003e If you want to follow this documentation to the end, this action is not recommended because the `Person` model following has a one-to-one relationship with `core.User` and you'll have to delete the user instance manually.\n\nif in this step, the additional fields you have added to the `core.User` take part, then you have changed the *auth flow* successfully.\n```\npython manage.py createsuperuser\n```\nyou can see/create users in the\n```\nlocalhost:8000/admin\n```\n\n\n## REST framework\n\ninstall it first:\n```\npip install djangorestframework\n```\nadd it to the installed apps in the `core.settings`\n\n```\n# core.settings\n\nINSTALLED_APPS = [\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n    'rest_framework',\n    'core',\n]\n```\n\nnow, you have rest framework in your project.\n\n**map:**\nthere are two models in the auth flow: `core.User` and `account.Person`. one includes the required fields for authentication such as email, phone number, password; so it stays the same in each project; but the `account.Person` will have different fields based on the project's requirements. we will build do the stuff related to `core.User`, then build the `account.Person` and its configuration and at last, we will connect them together so the user does not need two different forms to update their profile. the `core.User` uses djoser for the view-writing to prevent over-coding and `account.Person` will need its serializers and views. to implement the connection between the two, one approach is developing a new serializer-view-url and the other one is to write the serializer-view for the `account.Person` in a way that fetches the data from `core.User`, combine them with the `account.Person` data and show them all together to the user.\n\ntherefore, there will be 3 steps:\n1. creating the user model and its configurations\n2. creating the person model and its configurations\n3. connecting the two\n\n\n\n\n### install djoser and jwt\n\nrun:\n```\npip install djangorestframework_simplejwt\n```\n```\npip install djoser\n```\n\nregister djoser as an app:\n```\n# core.settings\n\nINSTALLED_APPS = [\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n    'rest_framework',\n    'djoser',\n    'core',\n]\n```\n\nadd the djoser url endpoints:\n```\n# core.urls\n\nfrom django.contrib import admin\nfrom django.urls import path, include\n\nurlpatterns = [\n    path('admin/', admin.site.urls),\n    path('auth/', include('djoser.urls')),\n    path('auth/', include('djoser.urls.jwt')),\n]\n```\n\nset jwt as the authenticatoin backend (you can add this at the bottom of the `core.settings`) 👇:\n\n\u003e [!NOTE]\n\u003e this setting sets the jwt authentication to auth layers of rest framework. without this, I don't think rest framework has any default auth security 👇.\n\n```\n# core.settings\n\nREST_FRAMEWORK = {\n    'DEFAULT_AUTHENTICATION_CLASSES': (\n        'rest_framework_simplejwt.authentication.JWTAuthentication',\n    ),\n}\n```\n\nand add:\n\n```\n# core.settings\n\nSIMPLE_JWT = {\n   'AUTH_HEADER_TYPES': ('JWT',),\n}\n```\n\n\u003e [!NOTE]\n\u003e with the code snippet above 👆, we determine in what format the jwt auth headers should be.\n\nor if you want more customized settings:\n```\n# core.settings\n\nfrom datetime import timedelta\n\nSIMPLE_JWT = {\n    'AUTH_HEADER_TYPES': ('JWT',),\n    \"ACCESS_TOKEN_LIFETIME\": timedelta(days=3),\n    \"REFRESH_TOKEN_LIFETIME\": timedelta(days=30),\n}\n```\n\nthe expiration date is three days for access token and a month for refresh token. the timedelta import and use is required because jwt can't work with int.\n\n\n\n**map:** the `core.User` and auth configuration is implemented. now, we implement the `person`(profile) model.\n\n\n\u003e [!NOTE]\n\u003e to get/test the current user's data, use the following url. this url will return the data of the user in the django auth system (`core.User`).\n    ```\n    localhost:8000/auth/users/me/\n    ```\n\n\n\n\u003e [!NOTE]\n\u003e with the current implementation, djoser has three main urls:\n    1. `auth/jwt/verify` for verifying the access token,\n    2. `auth/jwt/refresh` for getting a new access token (you'll need to provide a valid refresh token) and\n    3. `auth/jwt/create` for getting a new pair of access and refesh tokens (providing the login credentials (i.e., **email** and **password**)).\n\n\n## `account` app and `Person` (profile) model\n\nrun:\n```\npython manage.py startapp account\n```\n\nin `core.urls`, add the url for account app:\n```\n# core.urls\n\nfrom django.contrib import admin\nfrom django.urls import path, include\n\nurlpatterns = [\n    path('admin/', admin.site.urls),\n    path('auth/', include('djoser.urls')),\n    path('auth/', include('djoser.urls.jwt')),\n    \n    path('account/', include('account.urls')),\n]\n\n```\n\ncreate the `urls` file in the `account` app (does not include any urls for now):\n```\n# account.urls\n\nurlpatterns = [\n\n]\n```\n\nadd it to the installed apps:\n```\n# core.settings\n\nINSTALLED_APPS = [\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n    'rest_framework',\n    'core',\n    'account',\n]\n```\n\ncreate the `person` model:\n```\n# account.models\n\nfrom django.db import models\nfrom django.conf import settings\n\n\ngender_choices = (\n    ('M', 'Male'),\n    ('F', 'Female'),\n)\n\nclass Person(models.Model):\n    user = models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)\n    gender = models.CharField(max_length=1, choices=gender_choices, blank=True, null=True)\n    birth_date = models.DateField(blank=True, null=True)\n    bio = models.TextField(blank=True)\n\n    \n    updated_at = models.DateTimeField(auto_now=True)\n\n    def __str__(self):\n        return f'{self.user.first_name} {self.user.last_name}: {self.user.email}'\n```\n\n\n(optional) register the `account.Person` for the `admin` panel:\n```\n# account.admin\n\nfrom django.contrib import admin\nfrom .models import Person\n\nadmin.site.register(Person)\n```\n\nrun the migrations afterwise.\n\n\n**note:** \nthe following serializer is a complex serializer that combines the `User` and `Person` models. how? well, first it fetches the `User` data from the main app and then, combines it with the `Person` model.\n\n\nwrite the needed serializer-view-url:\n\n\n\n```\n# account.serializers\n\nfrom django.contrib.auth import get_user_model\nfrom rest_framework import serializers\n\nUser = get_user_model()\n\nclass CombinedUserPersonSerializer(serializers.ModelSerializer):\n    bio = serializers.CharField(source='person.bio', allow_blank=True)\n    birth_date = serializers.DateField(source='person.birth_date', allow_null=True)\n    gender = serializers.CharField(source='person.gender', allow_null=True)\n    updated_at = serializers.DateTimeField(source='person.updated_at', read_only=True)\n\n    class Meta:\n        model = User\n        fields = ['id', 'username', 'email', 'phone_number', 'date_joined', 'last_login', 'bio', 'birth_date', 'gender', 'updated_at',]\n        read_only_fields = ['id', 'username', 'date_joined', 'last_login',]\n```\n\n\n\n\n```\n# account.views\n\nfrom rest_framework import viewsets\nfrom rest_framework.mixins import RetrieveModelMixin, UpdateModelMixin\nfrom rest_framework.decorators import action\nfrom rest_framework.response import Response\nfrom rest_framework.permissions import IsAuthenticated\nfrom .serializers import CombinedUserPersonSerializer\nfrom django.contrib.auth import get_user_model\n\nUser  = get_user_model()\n\nclass CombinedUserProfileViewSet(RetrieveModelMixin, UpdateModelMixin, viewsets.GenericViewSet):\n    serializer_class = CombinedUserPersonSerializer\n    # the permission is to prevent the error caused by anonymous user (that has no email) to enter the view.\n    permission_classes = [IsAuthenticated]\n    \n    def get_queryset(self):\n        # Allow only the user or admin to access their profile\n        if self.request.user.is_staff:\n            return User.objects.all()\n        return User.objects.filter(id=self.request.user.id)\n\n    @action(detail=False, methods=['get', 'put', 'patch'])\n    def me(self, request):\n        user = request.user\n        if request.method == 'GET':\n            serializer = self.get_serializer(user)\n            return Response(serializer.data)\n        elif request.method in ['PUT', 'PATCH']:\n            serializer = self.get_serializer(user, data=request.data, partial=request.method == 'PATCH')\n            serializer.is_valid(raise_exception=True)\n            self.update_user_profile(user, serializer.validated_data)\n            return Response(serializer.data)\n\n    def update_user_profile(self, user, validated_data):\n        # Handle nested person data\n        person_data = validated_data.pop('person', {})\n\n        # Update the user instance\n        user = super().update(user, validated_data)\n\n        # Update the person instance if it exists\n        person = user.person\n        if person_data:\n            for attr, value in person_data.items():\n                setattr(person, attr, value)\n            person.save()\n\n        return user\n```\nabout the `me` action👆: this funtion returns the profile associated with the user model. therefore, the url `\u003csite-address\u003e/account/persons/me` returns the user's profile in a rest response. also, if the user is admin, he can see other profiles using `\u003csite-address\u003e/account/persons/\u003cpk\u003e` (this thing is done by the `get_queryset` method). this url also works if you own the profile with the `\u003cpk\u003e`.\n\n\n\n```\n# account.urls\n\nfrom django.urls import path, include\nfrom rest_framework.routers import DefaultRouter\nfrom .views import CombinedUserProfileViewSet\n\nrouter = DefaultRouter()\nrouter.register(r'persons', CombinedUserProfileViewSet, basename='user')\n\nurlpatterns = [\n    path(r'', include(router.urls)),\n]\n```\n\n\n\n\n\n\n\n\n\n**map:** both the profile and user models and their configurations are implemented; but they are not connected together. the next part (signals) takes care of that so when a user model is created, a profile model associated to it is automatically created. (this means that the `Person` and `User` models had to be connected manually before)\n\n## connecting the `User` and `Person` models\n\nin the `account` app, write in the `signals` file:\n\n```\n# account.signals\n\nfrom django.db.models.signals import post_save\nfrom django.dispatch import receiver\nfrom django.conf import settings\nfrom .models import Person\n@receiver(post_save, sender=settings.AUTH_USER_MODEL)\ndef create_person(sender, instance, created, **kwargs):\n    if created:\n        Person.objects.create(user=instance)\n@receiver(post_save, sender=settings.AUTH_USER_MODEL)\ndef save_person(sender, instance, **kwargs):\n    instance.person.save()\n```\n\nto get this `signal` run automatically, get it in the ready state:\n\n```\n# account.apps\n\nfrom django.apps import AppConfig\n\n\nclass AccountConfig(AppConfig):\n    default_auto_field = 'django.db.models.BigAutoField'\n    name = 'account'\n\n    def ready(self) -\u003e None:\n        import account.signals\n```\n\n\n\n\n\n\n\n\n\n\n\n**map:** now, the url `account/persons/me` containing the proper access token in the request header will return the `core.User` and `account.Person` all together. \n\n**note:** the email field in the `account.serializers` is not `read_only`, as some users may decide to change their emails. you can change it there.\n\n\ncongrats! you got yourself a jwt auth django project!\n\n\n\n## making the auth backend able to read the tokens from the cookies\n\n\nin the `account` app, create the `authentication.py`. this module reads the jwt token from the request cookies. the cookie has two main fields: `key` and `value`. the key must be `'jwt_access'` (it is modifiable and you can change it in this module) and the value must be the access token. Here comes the module:\n```\n#account.authentication.py\n\nfrom rest_framework_simplejwt.authentication import JWTAuthentication\nfrom rest_framework.exceptions import AuthenticationFailed\n\nclass CookieJWTAuthentication(JWTAuthentication):\n    def authenticate(self, request):\n        # First, try to authenticate using the standard method (from headers)\n        auth_result = super().authenticate(request)\n\n        if auth_result is not None:\n            return auth_result\n\n        # If no authentication was found in headers, check cookies\n        token = request.COOKIES.get('jwt_access')\n        if not token:\n            return None  # No token found in cookies\n\n        # Validate the token\n        validated_token = self.get_validated_token(token)\n\n        return self.get_user(validated_token), validated_token\n```\n\nadd this layer of auth to the rest framework auth layers (currently it is just jwt-reading-from-the-header layer). the order matters, so when the cookie layer is above the header layer, the project will look for the jwt token in the request cookies.\n```\n# core.settings\n\nREST_FRAMEWORK = {\n    'DEFAULT_AUTHENTICATION_CLASSES': (\n        'account.authentication.CookieJWTAuthentication',  # the custom cookie-reading authentication class\n        'rest_framework_simplejwt.authentication.JWTAuthentication',  # Fallback to default\n    ),\n}\n```\n\u003e [!NOTE]\n\u003e the project still is checking the request headers. if you don't want it, you can remove it from the `REST_FRAMEWORK` settings.\n\n\n\u003e [!NOTE]\n\u003e the cookie containing the jwt access token must be named `jwt_access`. if you want to change it, modify it in the `account.authentication`. \n\n\u003e [!IMPORTANT]\n\u003e this project does not set/save the cookie on the client (frontend can do it); therefore, if you want to test the implementation, you have to create the cookie manually.\n\n\u003e [!IMPORTANT]\n\u003e when retrieving a person model in the rest response (e.g. the `account/persons/me/` url), the `id` field is for the `core.User`, not the `account.Person`.\n\n\n\n\n\n\n\u003cbr\u003e\u003c/br\u003e\n\u003cbr\u003e\u003c/br\u003e\n\u003cbr\u003e\u003c/br\u003e\n\u003cbr\u003e\u003c/br\u003e\n\u003cbr\u003e\u003c/br\u003e\n\u003cbr\u003e\u003c/br\u003e\n\u003cbr\u003e\u003c/br\u003e\n\u003cbr\u003e\u003c/br\u003e\n\u003cbr\u003e\u003c/br\u003e\n\na big thanks to the AIs that helped me in this project;\n\n[blackbox.ai](https://www.blackbox.ai)\n\n[perplexity.ai](https://www.perplexity.ai/)\n\nand\n\nchatgpt and copilot (not quite a lot)\n\n\u003cbr\u003e\u003c/br\u003e\n\u003cbr\u003e\u003c/br\u003e\n\n\n\nthis markdown text was created on https://markdownlivepreview.com/\n\n\n\n\n\u003c!--\n\u003e [!NOTE]\n\u003e Useful information that users should know, even when skimming content.\n\n\u003e [!TIP]\n\u003e Helpful advice for doing things better or more easily.\n\n\u003e [!IMPORTANT]\n\u003e Key information users need to know to achieve their goal.\n\n\u003e [!WARNING]\n\u003e Urgent info that needs immediate user attention to avoid problems.\n\n\u003e [!CAUTION]\n\u003e Advises about risks or negative outcomes of certain actions.\n--\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhusein14azimi%2Fjwt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhusein14azimi%2Fjwt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhusein14azimi%2Fjwt/lists"}