### Install django-passkeys Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/index.md Install the core django-passkeys package using pip. ```bash pip install django-passkeys ``` -------------------------------- ### Navigate to Example Project Directory Source: https://github.com/ganiyevuz/django-passkeys/blob/main/Example.md Changes the current directory to the example project's root. ```shell cd example ``` -------------------------------- ### Start Development Server Source: https://github.com/ganiyevuz/django-passkeys/blob/main/Example.md Starts the Django development server. Ensure you use 'localhost' for WebAuthn compatibility. ```shell python manage.py runserver ``` -------------------------------- ### Start SSL Server Source: https://github.com/ganiyevuz/django-passkeys/blob/main/Example.md Starts the Django development server with SSL enabled using django-sslserver. ```shell python manage.py runsslserver ``` -------------------------------- ### Clone and Run Example Project Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/index.md Set up and run the included example project to test django-passkeys. Ensure you use 'localhost' for FIDO_SERVER_ID. ```bash git clone https://github.com/mkalioby/django-passkeys.git cd django-passkeys python -m venv env && source env/bin/activate pip install -r requirements.txt cd example python manage.py migrate python manage.py createsuperuser python manage.py runserver ``` -------------------------------- ### Install Project Requirements Source: https://github.com/ganiyevuz/django-passkeys/blob/main/Example.md Installs all necessary Python packages listed in the requirements file. ```shell pip install -r requirements.txt ``` -------------------------------- ### Install and Run with SSL Server Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/index.md Install django-sslserver and run the development server with HTTPS, which is required for production environments. ```bash pip install django-sslserver python manage.py runsslserver ``` -------------------------------- ### Install django-passkeys with DRF support Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/index.md Install django-passkeys with additional support for Django REST Framework. ```bash pip install django-passkeys[drf] ``` -------------------------------- ### Install Django SSL Server Source: https://github.com/ganiyevuz/django-passkeys/blob/main/Example.md Installs the django-sslserver package, required for running the project with HTTPS. ```shell pip install django-sslserver ``` -------------------------------- ### Install django-passkeys with DRF and JWT support Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/index.md Install django-passkeys with support for Django REST Framework and JSON Web Tokens. ```bash pip install django-passkeys[drf-jwt] ``` -------------------------------- ### Get Registration Options Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Send a POST request to get the WebAuthn `PublicKeyCredentialCreationOptions`. The server returns the challenge, relying party info, user info, and a signed `state_token` that ties the two steps together. ```APIDOC ## POST /api/passkeys/register/options ### Description Retrieves the necessary options for WebAuthn passkey registration, including a challenge and state token. ### Method POST ### Endpoint /api/passkeys/register/options ### Request Headers - **Authorization** (string) - Required - Bearer ### Response #### Success Response (200 OK) - **options.publicKey** (object) - Standard WebAuthn `PublicKeyCredentialCreationOptions`. - **options.publicKey.challenge** (string) - Base64URL-encoded challenge. - **options.publicKey.user.id** (string) - Base64URL-encoded user ID. - **options.publicKey.excludeCredentials** (array) - List of already-registered credential IDs. - **state_token** (string) - Signed server state, valid for 5 minutes. ### Response Example ```json { "options": { "publicKey": { "rp": { "id": "example.com", "name": "My App" }, "user": { "id": "am9obg==", "name": "john", "displayName": "John Doe" }, "challenge": "dGVzdC1jaGFsbGVuZ2U...", "pubKeyCredParams": [ {"type": "public-key", "alg": -7}, {"type": "public-key", "alg": -257} ], "excludeCredentials": [], "authenticatorSelection": { "residentKey": "preferred", "userVerification": "preferred" } } }, "state_token": "eyJhbGciOi..." } ``` ``` -------------------------------- ### Document Ready for Enrollment Source: https://github.com/ganiyevuz/django-passkeys/blob/main/passkeys/templates/passkeys/manage.html Executes the 'start' function when the document is ready, but only if the 'enroll' variable is true, typically indicating that a new passkey enrollment should be initiated. ```javascript {%if enroll%} $(document).ready(start) {%endif%} ``` -------------------------------- ### Passkey Session Information Example Source: https://github.com/ganiyevuz/django-passkeys/blob/main/README.md Example of the session data structure when a user has logged in with a passkey. It includes boolean status and details about the credential. ```json {"passkey": True, "name": "Chrome", "id": 2, "platform": "Chrome on Apple", "cross_platform": False} ``` -------------------------------- ### Install DRF Extra Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Install the necessary packages for DRF integration. Use 'django-passkeys[drf]' for standard DRF or 'django-passkeys[drf-jwt]' if using JWT authentication. ```bash pip install django-passkeys[drf] drf-spectacular ``` ```bash pip install django-passkeys[drf-jwt] drf-spectacular ``` -------------------------------- ### Non-Passkey Session Information Example Source: https://github.com/ganiyevuz/django-passkeys/blob/main/README.md Example of the session data structure when a user has not logged in with a passkey. The 'passkey' key is set to False. ```json {"passkey":False} ``` -------------------------------- ### Initiate Passkey Registration Modal Source: https://github.com/ganiyevuz/django-passkeys/blob/main/passkeys/templates/passkeys/manage.html Sets up the modal dialog for starting the passkey registration process. It populates the modal with a title, body text, and an input field for the key name, and adds a 'Start' button. ```javascript function start(){ $("#modal-title").html("Enter a token name") $("#modal-body").html( `

Please enter a name for your new token


` ) $("#actionBtn").remove(); $("#modal-footer").prepend(``) $("#popUpModal").modal('show') } ``` -------------------------------- ### JavaScript Passkey Registration and Authentication Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/jscode.md This comprehensive example demonstrates how to register a new passkey and authenticate a user using JavaScript's Fetch API. It includes helper functions for base64url encoding/decoding and handles the necessary API calls for both processes. Ensure you have the necessary authentication token for registration. ```javascript // ── Helper ── function base64urlToBuffer(base64url) { const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/'); const pad = base64.length % 4 === 0 ? '' : '='.repeat(4 - (base64.length % 4)); const binary = atob(base64 + pad); return Uint8Array.from(binary, c => c.charCodeAt(0)).buffer; } function bufferToBase64url(buffer) { const bytes = new Uint8Array(buffer); let binary = ''; bytes.forEach(b => binary += String.fromCharCode(b)); return btoa(binary).replace(/ /g, '-').replace(/ /g, '_').replace(/=+$/, ''); } // ── Register a new passkey ── async function registerPasskey(authToken, keyName = '') { // Step 1: Get options const optionsRes = await fetch('/api/passkeys/register/options', { method: 'POST', headers: { 'Authorization': `Bearer ${authToken}` }, }); const { options, state_token } = await optionsRes.json(); // Decode binary fields options.publicKey.challenge = base64urlToBuffer(options.publicKey.challenge); options.publicKey.user.id = base64urlToBuffer(options.publicKey.user.id); for (const cred of options.publicKey.excludeCredentials || []) { cred.id = base64urlToBuffer(cred.id); } // Step 2: Create credential const credential = await navigator.credentials.create(options); // Step 3: Verify const verifyRes = await fetch('/api/passkeys/register/verify', { method: 'POST', headers: { 'Authorization': `Bearer ${authToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ state_token, key_name: keyName, credential: { id: credential.id, rawId: bufferToBase64url(credential.rawId), response: { clientDataJSON: bufferToBase64url(credential.response.clientDataJSON), attestationObject: bufferToBase64url(credential.response.attestationObject), }, type: credential.type, }, }), }); return await verifyRes.json(); // { id, name, enabled, platform, ... } } // ── Authenticate with a passkey ── async function authenticateWithPasskey(username = null) { // Step 1: Get options const optionsRes = await fetch('/api/passkeys/authenticate/options', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(username ? { username } : {}), }); const { options, state_token } = await optionsRes.json(); // Decode binary fields options.publicKey.challenge = base64urlToBuffer(options.publicKey.challenge); for (const cred of options.publicKey.allowCredentials || []) { cred.id = base64urlToBuffer(cred.id); } // Step 2: Get assertion const assertion = await navigator.credentials.get(options); // Step 3: Verify const verifyRes = await fetch('/api/passkeys/authenticate/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ state_token, credential: { id: assertion.id, rawId: bufferToBase64url(assertion.rawId), response: { authenticatorData: bufferToBase64url(assertion.response.authenticatorData), clientDataJSON: bufferToBase64url(assertion.response.clientDataJSON), signature: bufferToBase64url(assertion.response.signature), userHandle: assertion.response.userHandle ? bufferToBase64url(assertion.response.userHandle) : null, }, type: assertion.type, }, }), }); return await verifyRes.json(); // { user_id, username, token_type, ... } } ``` ```javascript // Register (user must be logged in) const passkey = await registerPasskey(myAuthToken, 'My MacBook'); console.log('Registered:', passkey.name, passkey.platform); // Authenticate (passwordless — no login needed) const auth = await authenticateWithPasskey(); console.log('Logged in as:', auth.username); console.log('Token:', auth.access || auth.token); // JWT or DRF Token // Authenticate (username-assisted) const auth2 = await authenticateWithPasskey('john'); ``` -------------------------------- ### Register Options Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Get WebAuthn registration options. This is the first step in the registration flow, requiring user authentication. ```APIDOC ## POST register/options ### Description Get WebAuthn registration options. This is the first step in the registration flow, requiring user authentication. ### Method POST ### Endpoint /api/passkeys/register/options ### Parameters #### Request Body - **key_name** (string) - Required - The name for the new passkey. ### Request Example { "key_name": "My New Key" } ### Response #### Success Response (200) - **state_token** (string) - A token used to maintain state during the registration process. - **options** (object) - WebAuthn options for the browser to use. #### Response Example { "state_token": "some_state_token", "options": { "challenge": "some_challenge", "rp": {"name": "Django Passkeys"}, "user": {"id": "user_id", "name": "username"}, "pubKeyCredParams": [{"type": "public-key", "alg": -7}], "timeout": 60000, "authenticatorSelection": {"authenticatorAttachment": "platform", "userVerification": "preferred"} } } ``` -------------------------------- ### Immediate Mediation Form Setup Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/template-setup.md Include the passkeys.js script and a hidden form for Immediate Mediation, allowing passkeys to be used without a visible login form. ```html {%include 'passkeys/passkeys.js' allow_password=True %} ``` -------------------------------- ### Authentication Failed Error Example Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md A 401 Unauthorized response is returned when passkey authentication fails during the verify step. ```json {"detail": "Passkey authentication failed"} ``` -------------------------------- ### Not Found Error Example Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md A 404 Not Found response is returned if the specified passkey does not exist, is disabled, or does not belong to the authenticated user. ```json {"detail": "Passkey not found or disabled"} ``` -------------------------------- ### Get Authentication Options Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md This endpoint is used to retrieve the necessary options for initiating the passkey authentication process. It supports both discoverable (passwordless) and username-assisted authentication flows. ```APIDOC ## POST /api/passkeys/authenticate/options ### Description Retrieves authentication options for passkey login. Supports discoverable (passwordless) and username-assisted flows. ### Method POST ### Endpoint /api/passkeys/authenticate/options ### Parameters #### Request Body - **username** (string) - Optional - If provided, narrows the prompt to that user's registered passkeys. If omitted, the browser shows all discoverable passkeys for this domain. ### Request Example ```json {} ``` ```json {"username": "john"} ``` ### Response #### Success Response (200 OK) - **options** (object) - Contains the public key credentials and challenge for the browser. - **options.publicKey.challenge** (string) - Base64URL-encoded challenge. - **options.publicKey.allowCredentials** (array) - Credentials the browser should look for. Empty if no username was provided. - **options.publicKey.rpId** (string) - Relying party ID. - **userVerification** (string) - Indicates the user verification preference. - **state_token** (string) - Signed server state to be sent back in the verify step. Expires in 5 minutes. #### Response Example ```json { "options": { "publicKey": { "rpId": "example.com", "challenge": "cmFuZG9tLWNoYWxsZW5nZQ...", "allowCredentials": [ { "id": "credentialIdBase64url...", "type": "public-key" } ], "userVerification": "preferred" } }, "state_token": "eyJhbGciOi..." } ``` ``` -------------------------------- ### Configure FIDO Server Name Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/settings.md Set a human-readable name for your service, which will be shown to users during passkey registration. A lambda function can be used for multi-tenant setups. ```python FIDO_SERVER_NAME = "My App" ``` ```python FIDO_SERVER_NAME = lambda request: request.tenant.name ``` -------------------------------- ### Authenticate Options Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Get WebAuthn authentication options for users to log in. This endpoint is publicly accessible. ```APIDOC ## POST authenticate/options ### Description Get WebAuthn authentication options for users to log in. This endpoint is publicly accessible. ### Method POST ### Endpoint /api/passkeys/authenticate/options ### Parameters #### Request Body - **username** (string) - Required - The username of the user attempting to authenticate. ### Request Example { "username": "testuser" } ### Response #### Success Response (200) - **state_token** (string) - A token used to maintain state during the authentication process. - **options** (object) - WebAuthn options for the browser to use. #### Response Example { "state_token": "some_auth_state_token", "options": { "challenge": "some_auth_challenge", "rpId": "localhost", "allowCredentials": [{"id": "credential_id", "type": "public-key", "transports": ["internal", "hybrid"]}] } } ``` -------------------------------- ### Session Token Response Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Example response when using session-based authentication. No explicit token is returned; authentication is handled via session cookies. ```json { "user_id": 1, "username": "john", "token_type": "session" } ``` -------------------------------- ### Get Authentication Options - Discoverable Mode Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Send an empty JSON object to request authentication options for discoverable (passwordless) passkey login. The browser will prompt the user to select from their saved passkeys. ```json {} ``` -------------------------------- ### JWT Token Response Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Example response when using JWT for authentication. The 'access' token should be used in subsequent requests with the 'Authorization: Bearer ' header. ```json { "user_id": 1, "username": "john", "token_type": "jwt", "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } ``` -------------------------------- ### Verify Passkey and Get Token Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Verifies the WebAuthn assertion received from the browser and returns authentication tokens. This endpoint is crucial for completing the passkey login process. ```APIDOC ## POST /api/passkeys/authenticate/verify ### Description Verifies the WebAuthn assertion from the browser and returns authentication tokens. This endpoint completes the passkey login process. ### Method POST ### Endpoint /api/passkeys/authenticate/verify ### Parameters #### Request Body - **state_token** (string) - Required - The token from the options step. - **credential** (object) - Required - The WebAuthn assertion from `navigator.credentials.get()`. - **credential.id** (string) - Required - Base64URL-encoded credential ID — used to look up the passkey. - **credential.rawId** (string) - Required - Base64URL-encoded credential ID. - **credential.response** (object) - Required - The assertion response. - **credential.response.authenticatorData** (string) - Required - Base64URL-encoded authenticator data. - **credential.response.clientDataJSON** (string) - Required - Base64URL-encoded client data. - **credential.response.signature** (string) - Required - Base64URL-encoded signature. - **credential.response.userHandle** (string) - Optional - Base64URL-encoded user handle (may be null). - **credential.type** (string) - Required - The credential type, typically "public-key". ### Request Example { "state_token": "eyJhbGciOi...", "credential": { "id": "credentialIdBase64url...", "rawId": "credentialIdBase64url...", "response": { "authenticatorData": "SZYN5YgOjGh0NBcP...", "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0...", "signature": "MEUCIQC...", "userHandle": "am9obg==" }, "type": "public-key" } } ### Response #### Success Response (200 OK) - **user_id** (integer) - The ID of the authenticated user. - **username** (string) - The username of the authenticated user. - **token_type** (string) - The type of token issued (e.g., 'jwt', 'token', 'session'). - **access** (string) - The access token (for JWT). - **refresh** (string) - The refresh token (for JWT). - **token** (string) - The authentication token (for DRF Token). #### Response Example (JWT) { "user_id": 1, "username": "john", "token_type": "jwt", "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } #### Response Example (DRF Token) { "user_id": 1, "username": "john", "token_type": "token", "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b" } #### Response Example (Session) { "user_id": 1, "username": "john", "token_type": "session" } ### Error Responses - **400**: Invalid or expired state token. - **401**: Passkey authentication failed (invalid signature). - **404**: Credential ID not found or passkey is disabled. ``` -------------------------------- ### Get Authentication Options - Username-Assisted Mode Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Provide the username in the request body to narrow down the passkey prompt to that specific user's registered passkeys. This is useful when the user has already entered their username. ```json {"username": "john"} ``` -------------------------------- ### Verify Assertion and Get Token Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Sends the state token and the client-generated credential assertion to the server for verification. The server responds with a user ID, username, and an authentication token (JWT, DRF Token, or session). ```http POST /api/passkeys/authenticate/verify Content-Type: application/json ``` ```json { "state_token": "eyJhbGciOi...", "credential": { "id": "credentialIdBase64url...", "rawId": "credentialIdBase64url...", "response": { "authenticatorData": "SZYN5YgOjGh0NBcP...", "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0...", "signature": "MEUCIQC...", "userHandle": "am9obg==" }, "type": "public-key" } } ``` -------------------------------- ### Get Assertion in Browser Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Retrieves assertion options from the server and prompts the user for biometric or PIN authentication. Decodes binary fields and converts the assertion to a JSON-serializable object for sending back to the server. ```javascript const { options, state_token } = await response.json(); // Decode binary fields options.publicKey.challenge = base64url.decode(options.publicKey.challenge); for (let cred of options.publicKey.allowCredentials) { cred.id = base64url.decode(cred.id); } // Browser prompts user for biometric/PIN const assertion = await navigator.credentials.get(options); // Convert to JSON-serializable object const assertionJSON = { id: assertion.id, rawId: base64url.encode(assertion.rawId), response: { authenticatorData: base64url.encode(assertion.response.authenticatorData), clientDataJSON: base64url.encode(assertion.response.clientDataJSON), signature: base64url.encode(assertion.response.signature), userHandle: assertion.response.userHandle ? base64url.encode(assertion.response.userHandle) : null, }, type: assertion.type, }; ``` -------------------------------- ### JavaScript Function for Passkey Authentication Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md This JavaScript function handles the passkey authentication flow. It retrieves authentication options, gets the user's credential, and verifies it with the server. Implement the `no_credentials_found()` JavaScript function to handle cases where no credentials are found. ```javascript async function authn() { $.ajax({ "url": "/api/passkeys/authenticate/options", dataType: "json", method: "POST", data: {"csrfmiddlewaretoken": "{{ csrf_token }}"}, success: async function (data) { window.state_token = data.state_token user_data = await get_credential(data.options, true) if (user_data.type == "password") { console.log(user_data) alert("user is logging in with password, not passkey, forward to the "); return; } else { sendData = {state_token: window.state_token, credential: user_data.credential}, $.ajax({ url: "/api/passkeys/authenticate/verify", method: "POST", headers: {'X-CSRFToken': "{{ csrf_token }}"}, data: JSON.stringify(sendData), dataType: "json", contentType: "application/json; charset=utf-8", success: function (data) { alert("Login successfully as " + data.username); window.location.reload(); } }) } } }) } ``` -------------------------------- ### DRF Token Response Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Example response when using Django REST Framework's built-in token authentication. The token should be used in subsequent requests with the 'Authorization: Token ' header. ```json { "user_id": 1, "username": "john", "token_type": "token", "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b" } ``` -------------------------------- ### JavaScript Function for Passkey Registration Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md This JavaScript function handles the passkey registration flow, including getting options, verifying the credential, and sending data to the server. It uses jQuery for AJAX requests and prompts the user for a key name. ```javascript function registerKey() { key_name = prompt("Enter key name"); $.ajax({ "url": "/api/passkeys/register/options", dataType: "json", method: "POST", headers: {'X-CSRFToken': "{{ csrf_token }}"}, success: async function(data) { window.state_token = data.state_token try { key_info = await get_new_credentials(data.options) } catch(err) { alert("Failed registering new key, you can try again.") return } sendData= {key_name: key_name, state_token: window.state_token, credential: key_info} $.ajax({ url: "/api/passkeys/register/verify", method: "POST", headers: {'X-CSRFToken': "{{ csrf_token }}"}, data: JSON.stringify(sendData), contentType: "application/json; charset=utf-8", success: function(data){ alert("Key registered successfully"); showCredentials(); }, error:function(data) { alert("Error registering key"); } }) }, error: function(data) { alert("Error registering key"); } }) } ``` -------------------------------- ### Create Virtual Environment Source: https://github.com/ganiyevuz/django-passkeys/blob/main/Example.md Creates a new virtual environment for the project. ```shell python -m venv env ``` -------------------------------- ### POST Request for Registration Options Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Send a POST request to obtain WebAuthn `PublicKeyCredentialCreationOptions`. This includes the challenge, relying party info, user info, and a signed `state_token`. ```http POST /api/passkeys/register/options Authorization: Bearer ``` -------------------------------- ### Forbidden Error Example Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md A 403 Forbidden response occurs when a protected endpoint is accessed without a required authentication token. ```json {"detail": "Authentication credentials were not provided."} ``` -------------------------------- ### Validation Error Example Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md A 400 Bad Request response indicates a validation error, such as an expired or invalid state token. ```json {"detail": "Registration state has expired, please try again"} ``` -------------------------------- ### Configure Authentication Backend and FIDO Settings Source: https://github.com/ganiyevuz/django-passkeys/blob/main/README.md Set up your Django settings to use PasskeyModelBackend and configure FIDO server details. Ensure FIDO_SERVER_ID matches your domain for local development. ```python AUTHENTICATION_BACKENDS = ['passkeys.backend.PasskeyModelBackend'] # Change your authentication backend FIDO_SERVER_ID="localhost" # Server rp id for FIDO2, must match your domain FIDO_SERVER_NAME="TestApp" import passkeys KEY_ATTACHMENT = None # or passkeys.Attachment.CROSS_PLATFORM or passkeys.Attachment.PLATFORM ``` -------------------------------- ### Activate Virtual Environment Source: https://github.com/ganiyevuz/django-passkeys/blob/main/Example.md Activates the created virtual environment. ```shell source env/bin/activate ``` -------------------------------- ### Configure DRF Apps Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Add 'rest_framework' and 'passkeys' to your INSTALLED_APPS in settings.py. ```python # settings.py INSTALLED_APPS = [ ... 'rest_framework', 'passkeys', ] ``` -------------------------------- ### Response JSON for Registration Options Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md The server response contains `options` for WebAuthn and a `state_token` for tying registration steps together. ```json { "options": { "publicKey": { "rp": { "id": "example.com", "name": "My App" }, "user": { "id": "am9obm==", "name": "john", "displayName": "John Doe" }, "challenge": "dGVzdC1jaGFsbGVuZ2U...", "pubKeyCredParams": [ {"type": "public-key", "alg": -7}, {"type": "public-key", "alg": -257} ], "excludeCredentials": [], "authenticatorSelection": { "residentKey": "preferred", "userVerification": "preferred" } } }, "state_token": "eyJhbGciOi..." } ``` -------------------------------- ### Run Database Migrations Source: https://github.com/ganiyevuz/django-passkeys/blob/main/Example.md Applies database schema changes defined in the project's models. ```shell python manage.py migrate ``` -------------------------------- ### Document Ready for Passkey Check Source: https://github.com/ganiyevuz/django-passkeys/blob/main/example/test_app/templates/home.html This JavaScript code executes when the document is ready. It calls the `check_passkey` function, passing `true` and a callback function `register_pk` to handle passkey availability. ```javascript $(document).ready(check_passkey(true,register_pk)) ``` -------------------------------- ### Passkey Registration Logic Source: https://github.com/ganiyevuz/django-passkeys/blob/main/passkeys/templates/passkeys/manage.html Handles the client-side logic for registering a new passkey. It decodes challenge and user IDs, creates the credential, and sends the attestation to the server. Includes error handling and UI updates for success or failure. ```javascript var MakeCredReq = (makeCredReq) => { makeCredReq.publicKey.challenge = base64url.decode(makeCredReq.publicKey.challenge); makeCredReq.publicKey.user.id = base64url.decode(makeCredReq.publicKey.user.id); for(let excludeCred of makeCredReq.publicKey.excludeCredentials) { excludeCred.id = base64url.decode(excludeCred.id); } return makeCredReq } function begin_reg(){ fetch('{% url 'passkeys:reg_begin' %}',{}).then(function(response) { if(response.ok) { return response.json().then(function (req){ return MakeCredReq(req) }); } throw new Error('Error getting registration data!'); }).then(function(options) { //options.publicKey.attestation="direct" console.log(options) return navigator.credentials.create(options); }).then(function(attestation) { attestation["key_name"] = $("#key_name").val(); return fetch('{% url 'passkeys:reg_complete' %}', { headers:{'X-CSRFToken': "{{csrf_token}}"}, method: 'POST', body: JSON.stringify(publicKeyCredentialToJSON(attestation)) }); }).then(function(response) { var stat = response.ok ? 'successful' : 'unsuccessful'; return response.json() }).then(function (res) { if (res["status"] =='OK') $("#res").html("
Registered Successfully, Refresh
") else $("#res").html("
Registration Failed as " + res["message"] + ", try again
") }, function(reason) { $("#res").html("
Registration Failed as " +reason +". try again
") }) } ``` -------------------------------- ### Register Passkey JavaScript Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Include necessary JavaScript files for passkey registration. Ensure jQuery is loaded before these scripts. ```html ``` -------------------------------- ### Register New Passkey Flow Source: https://github.com/ganiyevuz/django-passkeys/blob/main/example/test_app/templates/rest/manage.html Initiates the passkey registration process by first requesting options from the server, then obtaining new credentials from the user's authenticator, and finally verifying the new key. ```javascript function registerKey() { key_name = prompt("Enter key name"); $.ajax({ "url": "/api/passkeys/register/options", dataType: "json", method: "POST", headers: {'X-CSRFToken': "{{ csrf_token }}"}, success: async function(data) { window.state_token = data.state_token try { key_info = await get_new_credentials(data.options) } catch(err) { if (err.name === 'InvalidStateError') { alert("This authenticator is already registered. Try a different device or security key.") } else if (err.name === 'NotAllowedError') { alert("Registration was cancelled or timed out.") } else { alert("Failed registering new key: " + err.message) } return } sendData= {key_name: key_name, state_token: window.state_token, credential: key_info} $.ajax({ url: "/api/passkeys/register/verify", method: "POST", headers: {'X-CSRFToken': "{{ csrf_token }}"}, data: JSON.stringify(sendData), contentType: "application/json; charset=utf-8", success: function(data){ alert("Key registered successfully"); showCredentials(); }, error:function(data) { alert("Error registering key"); } }) }, error: function(data) { alert("Error registering key"); } }) } ``` -------------------------------- ### Include JavaScript Files for Passkeys Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Ensure these JavaScript files are included in your HTML page. jQuery is a prerequisite. ```html ``` -------------------------------- ### Add 'passkeys' to INSTALLED_APPS Source: https://github.com/ganiyevuz/django-passkeys/blob/main/README.md Add the 'passkeys' application to your Django project's INSTALLED_APPS setting. ```python INSTALLED_APPS=( '......', 'passkeys', '......') ``` -------------------------------- ### Custom Token Backend Implementation Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Implement a custom token backend by creating a callable that accepts user and request, returning a dictionary for the authentication response. This is used when PASSKEYS_API_TOKEN_BACKEND is set in settings. ```python # myapp/auth.py def my_token_backend(user, request): """ Custom token backend. Must accept (user, request) and return a dict. The dict is merged into the authenticate/verify response. """ token = create_my_custom_token(user) return { 'token_type': 'custom', 'token': token, 'expires_in': 3600, } ``` -------------------------------- ### Show Passkey Element Source: https://github.com/ganiyevuz/django-passkeys/blob/main/example/test_app/templates/home.html This JavaScript function `register_pk` is used to show an element with the ID 'pk'. It's likely used as a callback when passkey support is detected. ```javascript function register_pk() { $('#pk').show(); } ``` -------------------------------- ### Authentication Options Response Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md The server response contains the `options` object, including `publicKey` details for the WebAuthn API, and a `state_token` for server-side state management. ```json { "options": { "publicKey": { "rpId": "example.com", "challenge": "cmFuZG9tLWNoYWxsZW5nZQ...", "allowCredentials": [ { "id": "credentialIdBase64url...", "type": "public-key" } ], "userVerification": "preferred" } }, "state_token": "eyJhbGciOi..." } ``` -------------------------------- ### Settings for Custom Token Backend Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Configure Django settings to use your custom token backend by specifying the `PASSKEYS_API_TOKEN_BACKEND` setting. ```python # settings.py PASSKEYS_API_TOKEN_BACKEND = 'myapp.auth.my_token_backend' ``` -------------------------------- ### Include Passkey Authentication Backend Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/settings.md Ensure the passkey backend is included in your Django AUTHENTICATION_BACKENDS setting. ```python AUTHENTICATION_BACKENDS = ['passkeys.backend.PasskeyModelBackend'] ``` -------------------------------- ### Create Superuser Source: https://github.com/ganiyevuz/django-passkeys/blob/main/Example.md Creates an administrative superuser account for accessing the Django admin interface. ```shell python manage.py createsuperuser ``` -------------------------------- ### Common Django Settings for Passkeys Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/index.md Configure your Django settings to use the PasskeyModelBackend and set FIDO server details. Ensure FIDO_SERVER_ID matches your domain. ```python INSTALLED_APPS = [ ... 'passkeys', ] AUTHENTICATION_BACKENDS = ['passkeys.backend.PasskeyModelBackend'] FIDO_SERVER_ID = "localhost" # (1) FIDO_SERVER_NAME = "My App" ``` -------------------------------- ### Collect Static Files Source: https://github.com/ganiyevuz/django-passkeys/blob/main/README.md Run the collectstatic management command to gather static files for the application. ```shell python manage.py collectstatic ``` -------------------------------- ### Check Platform Authenticator Enrollment Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/template-setup.md Display a message and enrollment link if the user's device supports passkeys and they can be enrolled. ```html ``` -------------------------------- ### Configure Key Attachment for Registration Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/settings.md Control which authenticator types are allowed during passkey registration using the KEY_ATTACHMENT setting. Defaults to None, allowing all types. ```python import passkeys KEY_ATTACHMENT = passkeys.Attachment.PLATFORM ``` -------------------------------- ### List all passkeys Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Retrieves a list of all passkeys associated with the authenticated user. This endpoint is used to display and manage the user's registered passkeys. ```APIDOC ## GET /api/passkeys/ ### Description Retrieves a list of all passkeys associated with the authenticated user. This endpoint is used to display and manage the user's registered passkeys. ### Method GET ### Endpoint /api/passkeys/ ### Parameters #### Headers - **Authorization** (string) - Required - Bearer token for authentication. ### Response #### Success Response (200 OK) - **id** (integer) - Passkey ID. - **name** (string) - User-given name or auto-detected platform. - **enabled** (boolean) - Whether this passkey can be used for login. - **platform** (string) - Detected platform: `Apple`, `Google`, `Microsoft`, `Chrome on Apple`, or `Key`. - **added_on** (datetime) - When the passkey was registered. - **last_used** (datetime/null) - Last successful authentication with this passkey. #### Response Example [ { "id": 1, "name": "My MacBook", "enabled": true, "platform": "Apple", "added_on": "2026-03-24T12:00:00Z", "last_used": "2026-03-25T09:30:00Z" }, { "id": 2, "name": "Work PC", "enabled": true, "platform": "Microsoft", "added_on": "2026-03-20T08:00:00Z", "last_used": null } ] ``` -------------------------------- ### Response JSON for Successful Verification Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md A successful verification returns a `201 Created` status with details of the newly registered passkey, including its ID, name, and status. ```json { "id": 1, "name": "My Laptop", "enabled": true, "platform": "Apple", "added_on": "2026-03-24T12:00:00Z", "last_used": null } ``` -------------------------------- ### POST Request for Credential Verification Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Send the created credential and the `state_token` back to the server for verification and saving. Ensure the `Content-Type` is set to `application/json`. ```http POST /api/passkeys/register/verify Authorization: Bearer Content-Type: application/json ``` -------------------------------- ### List All Passkeys Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Retrieves a list of all registered passkeys for the authenticated user. Requires authentication via a Bearer token. ```http GET /api/passkeys/ Authorization: Bearer ``` ```json [ { "id": 1, "name": "My MacBook", "enabled": true, "platform": "Apple", "added_on": "2026-03-24T12:00:00Z", "last_used": "2026-03-25T09:30:00Z" }, { "id": 2, "name": "Work PC", "enabled": true, "platform": "Microsoft", "added_on": "2026-03-20T08:00:00Z", "last_used": null } ] ``` -------------------------------- ### Verify and Save Credential Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/drf-setup.md Send the newly created WebAuthn credential and the state token back to the server to verify and save the passkey. ```APIDOC ## POST /api/passkeys/register/verify ### Description Verifies the newly created passkey credential with the server and saves it for the user. ### Method POST ### Endpoint /api/passkeys/register/verify ### Request Headers - **Authorization** (string) - Required - Bearer - **Content-Type** (string) - Required - application/json ### Parameters #### Request Body - **state_token** (string) - Yes - The token received from the options step. - **key_name** (string) - No - A human-readable name for the passkey (e.g., "Work Laptop"). Defaults to the detected platform. - **credential** (object) - Yes - The WebAuthn credential object obtained from `navigator.credentials.create()`. - **credential.id** (string) - Yes - Base64URL-encoded credential ID. - **credential.rawId** (string) - Yes - Base64URL-encoded raw credential ID. - **credential.response** (object) - Yes - The credential response object. - **credential.response.clientDataJSON** (string) - Yes - Base64URL-encoded client data. - **credential.response.attestationObject** (string) - Yes - Base64URL-encoded attestation object. - **credential.type** (string) - Yes - The type of credential, typically "public-key". ### Request Example ```json { "state_token": "eyJhbGciOi...", "key_name": "My Laptop", "credential": { "id": "credentialIdBase64url...", "rawId": "credentialIdBase64url...", "response": { "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3Jl...", "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10..." }, "type": "public-key" } } ``` ### Response #### Success Response (201 Created) - **id** (integer) - The unique identifier for the saved credential. - **name** (string) - The name of the credential. - **enabled** (boolean) - Indicates if the credential is enabled. - **platform** (string) - The platform associated with the credential. - **added_on** (string) - The timestamp when the credential was added. - **last_used** (string|null) - The timestamp when the credential was last used. #### Response Example ```json { "id": 1, "name": "My Laptop", "enabled": true, "platform": "Apple", "added_on": "2026-03-24T12:00:00Z", "last_used": null } ``` ### Error Responses - **400 Bad Request**: Invalid or expired state token, verification failed, or passkey already registered. - **401 Unauthorized / 403 Forbidden**: Authentication failed. ``` -------------------------------- ### Display Passkey Credentials Source: https://github.com/ganiyevuz/django-passkeys/blob/main/example/test_app/templates/rest/manage.html Fetches and displays a list of passkey credentials from the API. It generates an HTML table to show key details and provides options to toggle status or delete. ```javascript function showCredentials() { $.ajax({ "url":"/api/passkeys/", dataType: "json", success:function(data){ if (data.length == 0) { $("#creds").html("
No credentials found, please register a new key.
") return; } html=\` `; for (i in data) { key = data[i] status = `${key.enabled ? "Enabled" : "Disabled"} `; html+=\` `; } html+=\`
NameStateCreated AtLast Used...
${key.name}${status}${key.added_on}${key.last_used}
`; $("#creds").html(html); }, }) } ``` -------------------------------- ### Add Passkeys to Django URLs Source: https://github.com/ganiyevuz/django-passkeys/blob/main/docs/template-setup.md Include the passkeys app's URLs in your project's urls.py file. ```python urls_patterns= [ '...', path(r'^passkeys/', include('passkeys.urls')), '....', ] ```