├── .env.example ├── .gitignore ├── README.md ├── __init__.py ├── main ├── __init__.py ├── admin.py ├── apps.py ├── decorators.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_alter_user_city.py │ └── __init__.py ├── mixins.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── manage.py ├── project ├── __init__.py ├── asgi.py ├── context_processors.py ├── middleware.py ├── settings.py ├── templates │ ├── base.html │ └── main │ │ ├── home_authenticated.html │ │ ├── home_guest.html │ │ ├── login.html │ │ ├── onboarding.html │ │ ├── profile.html │ │ ├── signup.html │ │ ├── userarea_authenticated.html │ │ └── userarea_guest.html ├── urls.py ├── utils │ ├── __init__.py │ └── authentication.py └── wsgi.py └── requirements.txt /.env.example: -------------------------------------------------------------------------------- 1 | CORBADO_PROJECT_ID= 2 | CORBADO_API_SECRET= 3 | CORBADO_FRONTEND_API= 4 | CORBADO_BACKEND_API= 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | db.sqlite3 2 | .env* 3 | !.env.example 4 | __pycache__ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GitHub Repo Cover 2 | 3 | # Python Django Passkeys Example Application 4 | 5 | This is a sample implementation of the [Corbado passkeys-first authentication solution](https://www.corbado.com) using 6 | Python with Django. The following packages are being used: 7 | 8 | - [Corbado web-js](https://github.com/corbado/javascript/tree/develop/packages/web-js) 9 | - [Corbado Python](https://github.com/corbado/corbado-python) 10 | 11 | [![integration-guides](https://github.com/user-attachments/assets/7859201b-a345-4b68-b336-6e2edcc6577b)](https://app.corbado.com/integration-guides/python-django) 12 | 13 | ## File structure 14 | 15 | - `project`: The Django project 16 | - `project/settings.py`: The Django settings file 17 | - `project/context_processors.py`: The context processor that adds Corbado environment variables to the template context 18 | - `project/middleware.py`: The middleware that adds the authenticated user to the request object 19 | - `project/urls.py`: The Django project URL configuration 20 | - `project/templates`: The Django project templates 21 | - `project/templates/base.html`: The base template that acts as a layout to all other templates 22 | - `project/utils/authentication.py`: Provides utilities for authentication state 23 | - `main`: Our main application 24 | - `main/models.py`: Defines our custom user model 25 | - `main/views.py`: Contains the views for our application 26 | - `main/urls.py`: The URL configuration for our application 27 | - `main/decorators.py`: Decorator to force authentication on API routes 28 | - `main/mixins.py`: Mixin to force authentication on class-based views 29 | 30 | ## Setup 31 | 32 | ### Prerequisites 33 | 34 | Please follow the steps in [Getting started](https://docs.corbado.com/overview/getting-started) to create and configure 35 | a project in the [Corbado developer panel](https://app.corbado.com/). 36 | 37 | You need to have [Python](https://www.python.org/downloads/) and `pip` installed to run it. 38 | 39 | ### Configure environment variables 40 | 41 | Use the values you obtained in [Prerequisites](#prerequisites) to configure the following variables inside a `.env` 42 | file you create in the root folder of this project: 43 | 44 | ```sh 45 | CORBADO_PROJECT_ID=pro-XXX 46 | CORBADO_API_SECRET=corbado1_XXX 47 | CORBADO_FRONTEND_API=https://${CORBADO_PROJECT_ID}.frontendapi.cloud.corbado.io 48 | CORBADO_BACKEND_API=https://backendapi.cloud.corbado.io 49 | ``` 50 | 51 | ## Usage 52 | 53 | ### Run the project locally 54 | 55 | Run 56 | 57 | ```bash 58 | python -m venv venv 59 | ``` 60 | 61 | to create a virtual environment. 62 | 63 | Then, activate the virtual environment with 64 | 65 | ```bash 66 | source venv/bin/activate 67 | ``` 68 | 69 | or 70 | 71 | ```bash 72 | venv\Scripts\activate 73 | ``` 74 | 75 | on Windows. 76 | 77 | To install all dependencies, run 78 | 79 | ```bash 80 | pip install -r requirements.txt 81 | ``` 82 | 83 | Migrate your database by running 84 | 85 | ```bash 86 | python manage.py migrate 87 | ``` 88 | 89 | 90 | Now you can start the server by running 91 | 92 | ```bash 93 | python manage.py runserver 3000 94 | ``` 95 | 96 | ## Passkeys support 97 | 98 | - Community for Developer Support: https://bit.ly/passkeys-community 99 | - Passkeys Debugger: https://www.passkeys-debugger.io/ 100 | - Passkey Subreddit: https://www.reddit.com/r/passkey/ 101 | 102 | ## Telemetry 103 | 104 | This example application uses telemetry. By gathering telemetry data, we gain a more comprehensive understanding of how our SDKs, components, and example applications are utilized across various scenarios. This information is crucial in helping us prioritize features that are beneficial and impactful for the majority of our users. Read our [official documentation](https://docs.corbado.com/corbado-complete/other/telemetry) for more details. 105 | 106 | To disable telemetry, add the following line to your `.env` file: 107 | 108 | ```sh 109 | CORBADO_TELEMETRY_DISABLED=true 110 | ``` 111 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corbado/passkeys-python-django/9ce3f08ce3bebedc00b47b799a93e413da5b7873/__init__.py -------------------------------------------------------------------------------- /main/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corbado/passkeys-python-django/9ce3f08ce3bebedc00b47b799a93e413da5b7873/main/__init__.py -------------------------------------------------------------------------------- /main/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /main/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MainConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'main' 7 | -------------------------------------------------------------------------------- /main/decorators.py: -------------------------------------------------------------------------------- 1 | from django.http import JsonResponse 2 | from functools import wraps 3 | 4 | def api_login_required(view_func): 5 | @wraps(view_func) 6 | def _wrapped_view(request, *args, **kwargs): 7 | if request.corbado_user: 8 | return view_func(request, *args, **kwargs) 9 | return JsonResponse({'detail': 'Unauthorized'}, status=401) 10 | return _wrapped_view -------------------------------------------------------------------------------- /main/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.4 on 2024-12-30 10:12 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='User', 16 | fields=[ 17 | ('id', models.AutoField(primary_key=True, serialize=False)), 18 | ('corbado_user_id', models.CharField(max_length=255, unique=True)), 19 | ('city', models.CharField(max_length=255)), 20 | ('created_at', models.DateTimeField(auto_now_add=True)), 21 | ('updated_at', models.DateTimeField(auto_now=True)), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /main/migrations/0002_alter_user_city.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.4 on 2024-12-30 12:34 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='user', 15 | name='city', 16 | field=models.CharField(default=None, max_length=255, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /main/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corbado/passkeys-python-django/9ce3f08ce3bebedc00b47b799a93e413da5b7873/main/migrations/__init__.py -------------------------------------------------------------------------------- /main/mixins.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.mixins import AccessMixin 2 | from django.shortcuts import redirect 3 | 4 | class LoginRequiredMixin(AccessMixin): 5 | """Mixin that verifies that the current user is authenticated. 6 | Expects the user to be set by custom middleware (`request.corbado_user`). 7 | """ 8 | 9 | def dispatch(self, request, *args, **kwargs): 10 | if not request.corbado_user: 11 | return redirect('login') 12 | return super().dispatch(request, *args, **kwargs) -------------------------------------------------------------------------------- /main/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | class User(models.Model): 4 | id = models.AutoField(primary_key=True) 5 | corbado_user_id = models.CharField(unique=True, max_length=255) 6 | city = models.CharField(max_length=255, null=True, default=None) 7 | created_at = models.DateTimeField(auto_now_add=True) 8 | updated_at = models.DateTimeField(auto_now=True) 9 | 10 | def __str__(self): 11 | return f"{self.corbado_user_id} - {self.city}" -------------------------------------------------------------------------------- /main/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /main/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import * 3 | 4 | urlpatterns = [ 5 | path('', Home.as_view(), name='home'), 6 | path('signup/', Signup.as_view(), name='signup'), 7 | path('signup/onboarding/', Onboarding.as_view(), name='onboarding'), 8 | path('login/', Login.as_view(), name='login'), 9 | path('profile/', Profile.as_view(), name='profile'), 10 | path('userarea/', UserArea.as_view(), name='user_area'), 11 | path('api/secret/', secret_view, name='secret_view'), 12 | ] -------------------------------------------------------------------------------- /main/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect 2 | from django.views import View 3 | from .models import User 4 | from .decorators import api_login_required 5 | from .mixins import LoginRequiredMixin 6 | from django.http import JsonResponse 7 | from project.utils.authentication import get_user_identifiers 8 | 9 | 10 | # Create your views here. 11 | 12 | class Home(View): 13 | def get(self, request): 14 | if request.corbado_user: 15 | user = User.objects.get(corbado_user_id=request.corbado_user.user_id) 16 | return render( 17 | request, 'main/home_authenticated.html', 18 | {'city': user.city} 19 | ) 20 | return render(request, 'main/home_guest.html') 21 | 22 | 23 | class Signup(View): 24 | def get(self, request): 25 | if request.corbado_user: 26 | return redirect('profile') 27 | return render(request, 'main/signup.html') 28 | 29 | 30 | class Onboarding(LoginRequiredMixin, View): 31 | def get(self, request): 32 | user, _ = User.objects.get_or_create(corbado_user_id=request.corbado_user.user_id) 33 | if user.city is not None: 34 | return redirect('profile') 35 | return render(request, 'main/onboarding.html') 36 | 37 | def post(self, request): 38 | user = User.objects.get(corbado_user_id=request.corbado_user.user_id) 39 | user.city = request.POST['city'] 40 | user.save() 41 | return redirect('home') 42 | 43 | 44 | class Login(View): 45 | def get(self, request): 46 | if request.corbado_user: 47 | return redirect('profile') 48 | return render(request, 'main/login.html') 49 | 50 | 51 | class UserArea(View): 52 | def get(self, request): 53 | if request.corbado_user: 54 | return render(request, 'main/userarea_authenticated.html') 55 | return render(request, 'main/userarea_guest.html') 56 | 57 | 58 | class Profile(LoginRequiredMixin, View): 59 | def get(self, request): 60 | user = User.objects.get(corbado_user_id=request.corbado_user.user_id) 61 | identifiers = get_user_identifiers(request.corbado_user.user_id) 62 | return render( 63 | request, 'main/profile.html', 64 | {'example_id': user.id, 'corbado_id': user.corbado_user_id, 'identifiers': identifiers.identifiers} 65 | ) 66 | 67 | @api_login_required 68 | def secret_view(request): 69 | return JsonResponse({'secret': 'Passkeys are cool!'}) -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corbado/passkeys-python-django/9ce3f08ce3bebedc00b47b799a93e413da5b7873/project/__init__.py -------------------------------------------------------------------------------- /project/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for app project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /project/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | # scs name: 5 | def corbado_settings(request): 6 | return { 7 | 'CORBADO_PROJECT_ID': settings.CORBADO_PROJECT_ID, 8 | 'CORBADO_TELEMETRY_DISABLED': str(settings.CORBADO_TELEMETRY_DISABLED == 'true').lower(), 9 | 'CORBADO_FRONTEND_API': settings.CORBADO_FRONTEND_API, 10 | 'corbado_user': request.corbado_user 11 | } 12 | -------------------------------------------------------------------------------- /project/middleware.py: -------------------------------------------------------------------------------- 1 | from .utils.authentication import get_authenticated_user_from_cookie, get_authenticated_user_from_authorization_header 2 | from dataclasses import dataclass 3 | from django.utils.deprecation import MiddlewareMixin 4 | 5 | @dataclass(frozen=True) 6 | class CorbadoUser: 7 | user_id: str 8 | full_name: str 9 | 10 | 11 | class CorbadoAuthenticationMiddleware(MiddlewareMixin): 12 | def process_request(self, request): 13 | request.corbado_user = None 14 | validation_result = get_authenticated_user_from_cookie(request) 15 | if not validation_result: 16 | validation_result = get_authenticated_user_from_authorization_header(request) 17 | if validation_result: 18 | request.corbado_user = CorbadoUser(user_id=validation_result.user_id, full_name=validation_result.full_name) -------------------------------------------------------------------------------- /project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for project. 3 | 4 | Generated by 'django-admin startproject' using Django 5.1.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/5.1/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | import os 15 | import environ 16 | 17 | # SECURITY WARNING: don't run with debug turned on in production! 18 | DEBUG = True 19 | 20 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 21 | BASE_DIR = Path(__file__).resolve().parent.parent 22 | 23 | # Initialize environment variables 24 | env = environ.Env() 25 | 26 | # Take environment variables from .env file 27 | environ.Env.read_env(env_file=BASE_DIR / '.env') 28 | 29 | # Corbado Settings 30 | CORBADO_PROJECT_ID = env('CORBADO_PROJECT_ID') 31 | CORBADO_API_SECRET = env('CORBADO_API_SECRET') 32 | CORBADO_FRONTEND_API = env('CORBADO_FRONTEND_API') 33 | CORBADO_BACKEND_API = env('CORBADO_BACKEND_API') 34 | CORBADO_TELEMETRY_DISABLED = env.str('CORBADO_TELEMETRY_DISABLED', 'false') 35 | 36 | # Quick-start development settings - unsuitable for production 37 | # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ 38 | 39 | # SECURITY WARNING: keep the secret key used in production secret! 40 | SECRET_KEY = 'django-insecure-uqjk#s7p6i81e-9(j_0!vm55y4$+_ga=pi3i$9g8_wohpm)_lp' 41 | 42 | ALLOWED_HOSTS = [] 43 | 44 | # Application definition 45 | 46 | INSTALLED_APPS = [ 47 | 'django.contrib.admin', 48 | 'django.contrib.auth', 49 | 'django.contrib.contenttypes', 50 | 'django.contrib.sessions', 51 | 'django.contrib.messages', 52 | 'django.contrib.staticfiles', 53 | # User defined apps 54 | 'main' 55 | ] 56 | 57 | MIDDLEWARE = [ 58 | 'django.middleware.security.SecurityMiddleware', 59 | 'django.contrib.sessions.middleware.SessionMiddleware', 60 | 'django.middleware.common.CommonMiddleware', 61 | 'django.middleware.csrf.CsrfViewMiddleware', 62 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 63 | 'django.contrib.messages.middleware.MessageMiddleware', 64 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 65 | # User defined middleware 66 | 'project.middleware.CorbadoAuthenticationMiddleware' 67 | ] 68 | 69 | ROOT_URLCONF = 'project.urls' 70 | 71 | TEMPLATES = [ 72 | { 73 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 74 | 'DIRS': [ 75 | os.path.join(BASE_DIR, 'project', 'templates'), # Add this line 76 | ], 77 | 'APP_DIRS': True, 78 | 'OPTIONS': { 79 | 'context_processors': [ 80 | 'django.template.context_processors.debug', 81 | 'django.template.context_processors.request', 82 | 'django.contrib.auth.context_processors.auth', 83 | 'django.contrib.messages.context_processors.messages', 84 | # Custom context processor 85 | 'project.context_processors.corbado_settings' 86 | ], 87 | }, 88 | }, 89 | ] 90 | 91 | WSGI_APPLICATION = 'project.wsgi.application' 92 | 93 | # Database 94 | # https://docs.djangoproject.com/en/5.1/ref/settings/#databases 95 | 96 | DATABASES = { 97 | 'default': { 98 | 'ENGINE': 'django.db.backends.sqlite3', 99 | 'NAME': BASE_DIR / 'db.sqlite3', 100 | } 101 | } 102 | 103 | # Password validation 104 | # https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators 105 | 106 | AUTH_PASSWORD_VALIDATORS = [ 107 | { 108 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 109 | }, 110 | { 111 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 112 | }, 113 | { 114 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 115 | }, 116 | { 117 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 118 | }, 119 | ] 120 | 121 | # Internationalization 122 | # https://docs.djangoproject.com/en/5.1/topics/i18n/ 123 | 124 | LANGUAGE_CODE = 'en-us' 125 | 126 | TIME_ZONE = 'UTC' 127 | 128 | USE_I18N = True 129 | 130 | USE_TZ = True 131 | 132 | # Static files (CSS, JavaScript, Images) 133 | # https://docs.djangoproject.com/en/5.1/howto/static-files/ 134 | 135 | STATIC_URL = '/static/' 136 | STATICFILES_DIRS = [BASE_DIR / 'static'] 137 | 138 | # Default primary key field type 139 | # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field 140 | 141 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 142 | -------------------------------------------------------------------------------- /project/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% load static %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | {% block title %}Corbado Example{% endblock %} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 63 | 64 | 78 | 79 | 80 | 81 |
82 | 126 |
127 | 128 |
129 |
130 | {% block content %} 131 | 132 | {% endblock %} 133 |
134 | 144 |
145 | 146 | {% if corbado_user %} 147 | 154 | {% endif %} 155 | 156 | 157 | -------------------------------------------------------------------------------- /project/templates/main/home_authenticated.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |

Welcome from {{city}}!

6 |

You now have access to everything and can visit the user area:

7 | User area 8 |
9 | 14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /project/templates/main/home_guest.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |

Welcome Guest!

6 |

This example demonstrates Corbado's passkey-first authentication solution.

7 |

It covers all relevant aspects like -

8 |
    9 |
  • Sign-up
  • 10 |
  • Login
  • 11 |
  • Protecting Routes
  • 12 |
13 |

It can be used as a starting point for your own application or to learn.

14 | Sign up 15 | Login 16 |
17 | {% endblock %} -------------------------------------------------------------------------------- /project/templates/main/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |

Login

6 |
7 |
8 | 19 | {% endblock %} -------------------------------------------------------------------------------- /project/templates/main/onboarding.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

Onboarding

5 |

Choose your city

6 |
7 | {% csrf_token %} 8 | 9 | 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /project/templates/main/profile.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |

Profile

6 |

Example userID: {{ example_id }}

7 |

Corbado userID: {{ corbado_id }}

8 |

Your Identifiers

9 |
10 | {% for identifier in identifiers %} 11 |
12 |

13 | Type: {{ identifier.type.value }} 14 |

15 |

16 | Value: {{ identifier.value }} 17 |

18 |
19 | {% endfor %} 20 |
21 |

Manage your Passkeys

22 |
23 | 28 |
29 | {% endblock %} -------------------------------------------------------------------------------- /project/templates/main/signup.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |

Signup

6 |
7 |
8 | 19 | {% endblock %} -------------------------------------------------------------------------------- /project/templates/main/userarea_authenticated.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | 4 | {% block content %} 5 |
6 |

User area!

7 |

Since you are logged-in, we can tell you a secret:

8 | 9 |
10 |
11 | 42 | {% endblock %} -------------------------------------------------------------------------------- /project/templates/main/userarea_guest.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |

User area!

6 |

This page is for logged-in users only. Please login:

7 | Login 8 |
9 | {% endblock %} -------------------------------------------------------------------------------- /project/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for app project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/5.1/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | from django.contrib import admin 18 | from django.urls import path, include, re_path 19 | from django.conf import settings 20 | from django.views.static import serve as static_serve 21 | 22 | urlpatterns = [ 23 | path('admin/', admin.site.urls), 24 | path('', include('main.urls')), 25 | # workaround to have favicon.ico under the root url 26 | re_path(r'^favicon\.ico$', static_serve, { 27 | 'path': 'favicon.ico', 28 | 'document_root': settings.STATICFILES_DIRS[0], 29 | }), 30 | ] 31 | -------------------------------------------------------------------------------- /project/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corbado/passkeys-python-django/9ce3f08ce3bebedc00b47b799a93e413da5b7873/project/utils/__init__.py -------------------------------------------------------------------------------- /project/utils/authentication.py: -------------------------------------------------------------------------------- 1 | from corbado_python_sdk.generated.models import IdentifierList 2 | from django.conf import settings 3 | from django.http import HttpRequest 4 | from corbado_python_sdk import Config, CorbadoSDK, UserEntity 5 | 6 | config = Config( 7 | project_id=settings.CORBADO_PROJECT_ID, 8 | api_secret=settings.CORBADO_API_SECRET, 9 | frontend_api=settings.CORBADO_FRONTEND_API, 10 | backend_api=settings.CORBADO_BACKEND_API 11 | ) 12 | sdk = CorbadoSDK(config=config) 13 | 14 | 15 | def get_authenticated_user_from_cookie(req: HttpRequest) -> UserEntity | None: 16 | session_token = req.COOKIES.get('cbo_session_token') 17 | if not session_token: 18 | return None 19 | try: 20 | return sdk.sessions.validate_token(session_token) 21 | except: 22 | # use more sophisticated error handling in production 23 | return None 24 | 25 | 26 | def get_authenticated_user_from_authorization_header(req: HttpRequest) -> UserEntity | None: 27 | session_token = req.headers.get('Authorization') 28 | if not session_token: 29 | return None 30 | try: 31 | return sdk.sessions.validate_token(session_token) 32 | except: 33 | # use more sophisticated error handling in production 34 | return None 35 | 36 | 37 | 38 | def get_user_identifiers(user_id: str) -> IdentifierList: 39 | return sdk.identifiers.list_identifiers(user_id=user_id) 40 | -------------------------------------------------------------------------------- /project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for app project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | annotated-types==0.7.0 2 | asgiref==3.8.1 3 | cffi==1.17.1 4 | cryptography==44.0.0 5 | Django==5.1.4 6 | django-environ==0.11.2 7 | passkeys==2.0.0 8 | pycparser==2.22 9 | pydantic==2.10.4 10 | pydantic_core==2.27.2 11 | PyJWT==2.10.1 12 | pyOpenSSL==24.3.0 13 | python-dateutil==2.9.0.post0 14 | six==1.17.0 15 | sqlparse==0.5.3 16 | typing_extensions==4.12.2 17 | urllib3==2.3.0 18 | --------------------------------------------------------------------------------