Skip to content

feat: Update to Django 4+ and add README #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 63 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,69 @@
# Implementing Facebook login for DJango web using JWT Authentication Flow
# Django Facebook Registration Project

This repo contains code for the tutorial. Complete tutorial at https://www.trysudo.com/how-to-add-facebook-login-to-django-using-json-web-tokens/
This project is a Django application that allows users to register and log in using their Facebook account. It also provides an API endpoint to retrieve user details.

Demo at: https://sample-login-codepal.herokuapp.com/
## Setup Instructions

1. **Clone the repository:**
```bash
git clone <repository_url>
cd <repository_directory>
```

## License: MIT
2. **Create and activate a virtual environment:**
```bash
python3 -m venv venv
source venv/bin/activate # On Windows use `venv\Scripts\activate`
```

3. **Install dependencies:**
```bash
pip install -r requirements.txt
```

4. **Set up the database:**
This project uses SQLite by default (as per the settings when no `DATABASE_URL` is provided).
Run migrations to create the necessary database tables:
```bash
python manage.py migrate
```
If you want to use PostgreSQL, set the `DATABASE_URL` environment variable. For example:
`export DATABASE_URL="postgres://user:password@host:port/dbname"`

5. **Create a superuser (optional):**
To access the Django admin interface, create a superuser:
```bash
python manage.py createsuperuser
```

## Running the Development Server

1. **Start the server:**
```bash
python manage.py runserver
```
The application will typically be available at `http://127.0.0.1:8000/`.

## Available API Endpoints

The following API endpoints are available:

* **`POST /api/v1/user/register/facebook`**:
* Registers or logs in a user via their Facebook access token.
* Expects a JSON payload with `{"access_token": "your_facebook_access_token", "userID": "facebook_user_id"}`. (Note: Added `userID` as it's used in `registration/views.py`)
* Returns an access token in the JSON response and sets it in an HTTP-only cookie.

* **`GET /api/v1/user/get/account`**:
* Retrieves the details of the authenticated user (name, email, Facebook ID).
* Requires a valid JWT access token (typically sent in an HTTP-only cookie after login, or in the `Authorization: Bearer <token>` header).

* **`POST /api/token/`**: (Note: Path changed from `/api-token-auth/` during simplejwt migration)
* Obtains JWT tokens (access and refresh). This is the standard endpoint from `rest_framework_simplejwt.views.TokenObtainPairView`.
* Expects a JSON payload with `{"username": "your_username", "password": "your_password"}` for users created via Django admin or other means (not Facebook login).

## Running Tests

To run the test suite (currently empty):
```bash
python manage.py test
```
41 changes: 35 additions & 6 deletions my_project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
'whitenoise.runserver_nostatic',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_simplejwt',
'registration'
]

Expand Down Expand Up @@ -140,18 +141,46 @@
# Rest framework permissions
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
}

# JWT Settings
# For JWT, define an auth cookie name
JWT_AUTH_COOKIE = 'jwt_auth_token'
# JWT_AUTH_COOKIE = 'jwt_auth_token' # No longer used by simplejwt
# Set when token expires.
JWT_EXPIRATION_DELTA = datetime.timedelta(days=60)

JWT_AUTH = {'JWT_AUTH_COOKIE': JWT_AUTH_COOKIE,
'JWT_EXPIRATION_DELTA': JWT_EXPIRATION_DELTA
# JWT_EXPIRATION_DELTA = datetime.timedelta(days=60) # Configure SIMPLE_JWT settings instead

# JWT_AUTH = {'JWT_AUTH_COOKIE': JWT_AUTH_COOKIE, # No longer used by simplejwt
# 'JWT_EXPIRATION_DELTA': JWT_EXPIRATION_DELTA
# }

SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': datetime.timedelta(days=60),
'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=1), # Example, adjust as needed
'ROTATE_REFRESH_TOKENS': False, # Example, adjust as needed
'BLACKLIST_AFTER_ROTATION': True, # Example, adjust as needed
'UPDATE_LAST_LOGIN': False, # Example, adjust as needed

'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,

'AUTH_HEADER_TYPES': ('Bearer',),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',

'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',

'JTI_CLAIM': 'jti',

'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': datetime.timedelta(minutes=5), # Example, adjust as needed
'SLIDING_TOKEN_REFRESH_LIFETIME': datetime.timedelta(days=1), # Example, adjust as needed
}


Expand Down
14 changes: 7 additions & 7 deletions my_project/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,26 @@
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
from django.urls import re_path
from django.contrib import admin
from django.views.generic import TemplateView
from rest_framework_jwt.views import obtain_jwt_token
from rest_framework_simplejwt.views import TokenObtainPairView

from registration.views import register_user_via_facebook, get_user_details

urlpatterns = [
url(r'^admin/', admin.site.urls),
re_path(r'^admin/', admin.site.urls),

url(r'^api-token-auth/', obtain_jwt_token),
re_path(r'^api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),

# Url for facebook signup
url(r'^api/v1/user/register/facebook', register_user_via_facebook),
re_path(r'^api/v1/user/register/facebook', register_user_via_facebook),

# Url to fetch user details
url(r'^api/v1/user/get/account', get_user_details),
re_path(r'^api/v1/user/get/account', get_user_details),




url(r'^$', TemplateView.as_view(template_name='home.html')),
re_path(r'^$', TemplateView.as_view(template_name='home.html')),
]
2 changes: 1 addition & 1 deletion registration/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

class FbAuth(models.Model):
"""Stores Fb auth details for users who sign in via facebook"""
user = models.OneToOneField(User)
user = models.OneToOneField(User, on_delete=models.CASCADE)
facebook_token = models.CharField(verbose_name="Facebook auth token", max_length=600, null=False, blank=False, unique=False)
facebook_id = models.CharField(verbose_name="Facebook id", max_length=20, null=False, blank=False, unique=False)
last_modified = models.DateTimeField(auto_now=True, null=False, blank=False)
Expand Down
26 changes: 19 additions & 7 deletions registration/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
from rest_framework.decorators import api_view, permission_classes
from rest_framework.exceptions import APIException
from rest_framework.permissions import IsAuthenticated
from rest_framework_jwt.settings import api_settings
from rest_framework_simplejwt.tokens import RefreshToken
from django.conf import settings # To access SIMPLE_JWT settings

from my_project.settings import JWT_AUTH_COOKIE, JWT_EXPIRATION_DELTA
from registration.models import FbAuth


Expand All @@ -33,13 +33,25 @@ def register_user_via_facebook(request):
FbAuth.create_or_update(user, data['userID'], data['accessToken'])

# Step 3: Return JWT token in cookie.

payload = api_settings.JWT_PAYLOAD_HANDLER(user)
token = api_settings.JWT_ENCODE_HANDLER(payload)
refresh = RefreshToken.for_user(user)
access_token = str(refresh.access_token)

# Step 4: Set token as cookie
response = JsonResponse({'token': token}, safe=False, status=status.HTTP_200_OK)
response.set_cookie(JWT_AUTH_COOKIE, token, JWT_EXPIRATION_DELTA.total_seconds(), httponly=True)
response = JsonResponse({'token': access_token}, safe=False, status=status.HTTP_200_OK)

# Use a generic cookie name, can be configured in settings if needed
auth_cookie_name = getattr(settings, 'SIMPLE_JWT', {}).get('AUTH_COOKIE', 'jwt_auth_token')
# Use access token lifetime from settings
access_token_lifetime = getattr(settings, 'SIMPLE_JWT', {}).get('ACCESS_TOKEN_LIFETIME')

if access_token_lifetime:
response.set_cookie(
auth_cookie_name,
access_token,
max_age=access_token_lifetime.total_seconds(),
httponly=True,
samesite=getattr(settings, 'SIMPLE_JWT', {}).get('AUTH_COOKIE_SAMESITE', 'Lax') # Add SameSite attribute
)

return response

Expand Down
16 changes: 8 additions & 8 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
appdirs==1.4.3
dj-database-url==0.4.1
Django==1.10.4
djangorestframework==3.6.2
djangorestframework-jwt==1.10.0
Django>=4.0,<5.0
djangorestframework>=3.12,<3.15
djangorestframework-simplejwt>=5.0,<5.4
facebook-sdk==2.0.0
gunicorn==19.6.0
packaging==16.8
psycopg2==2.6.2
PyJWT==1.4.2
psycopg2-binary>=2.8,<2.10
PyJWT>=2.0,<2.9
pyparsing==2.2.0
requests==2.13.0
six==1.10.0
whitenoise==3.2
requests>=2.25.0,<2.32.0
six>=1.15,<1.17
whitenoise>=6.0,<6.7