|
| 1 | +.. _keeping_passkeys_in_sync: |
| 2 | + |
| 3 | +Keeping user details in sync with Passkeys |
| 4 | +========================================== |
| 5 | + |
| 6 | +When your users make changes to their details, such as their email address or |
| 7 | +username, these changes won't automatically be reflected in the Passkeys they |
| 8 | +have saved in their browser. This can lead to confusion as their old email or |
| 9 | +username may still appear during Passkey authentication. |
| 10 | + |
| 11 | +To ensure a smooth user experience, it's important to keep the user details |
| 12 | +associated with Passkeys up to date. Django OTP WebAuthn has a template tag that |
| 13 | +can help with this by calling the right browser APIs to update the stored |
| 14 | +Passkeys for you. |
| 15 | + |
| 16 | + |
| 17 | +.. _the_render_otp_webauthn_sync_signals_scripts_template_tag: |
| 18 | + |
| 19 | +The ``render_otp_webauthn_sync_signals_scripts`` template tag |
| 20 | +------------------------------------------------------------- |
| 21 | + |
| 22 | +This is a lightweight template tag that you can add to your base template. It |
| 23 | +looks for a key in the session that indicates that user details have changed, |
| 24 | +and if so, it renders the necessary JavaScript to update the stored Passkeys in |
| 25 | +the user's browser. If no syncing is needed, it outputs nothing so it won't |
| 26 | +unnecessarily bloat your pages. |
| 27 | + |
| 28 | +To use this template tag, you would add it near the bottom of your base |
| 29 | +template, just before the closing ``</body>`` tag. This way it won't block the |
| 30 | +initial page load, but will still be executed when the page is fully loaded. |
| 31 | + |
| 32 | +.. code-block:: html |
| 33 | + |
| 34 | + <!-- base.html --> |
| 35 | + {% load otp_webauthn %} |
| 36 | + ... |
| 37 | + <html> |
| 38 | + ... |
| 39 | + <body> |
| 40 | + ... |
| 41 | + ... |
| 42 | + {% render_otp_webauthn_sync_signals_scripts %} |
| 43 | + </body> |
| 44 | + </html> |
| 45 | + |
| 46 | +How it works |
| 47 | +------------ |
| 48 | + |
| 49 | +When a user authenticates using a Passkey or registers a new Passkey, the |
| 50 | +default authentication and registration views will automatically call |
| 51 | +``django_otp_webauthn.utils.request_user_details_sync`` after successful |
| 52 | +registration or authentication respectively. This function sets a flag in the |
| 53 | +user's session indicating that their details need to be synchronized. This is |
| 54 | +the official way to request a user details sync from Django OTP WebAuthn. |
| 55 | + |
| 56 | +On the next page load, when templates are being rendered, the template tag sees |
| 57 | +this flag and outputs JavaScript code that uses the appropriate WebAuthn API to |
| 58 | +update the stored Passkeys with the latest user details. After the |
| 59 | +synchronization is complete, the flag is cleared from the session to prevent |
| 60 | +rendering unnecessary the JavaScript on subsequent page loads. |
| 61 | + |
| 62 | +Removing deleted Passkeys from the browser |
| 63 | +------------------------------------------ |
| 64 | + |
| 65 | +If a user removes a Passkey from your application – most likely through an |
| 66 | +account settings page that you have created – this removal won't automatically |
| 67 | +be reflected in the Passkeys stored in their browser. Meaning the Passkey will |
| 68 | +still show up the next time the user tries to authenticate. |
| 69 | + |
| 70 | +To deal with this appropriately, you should call the |
| 71 | +``request_user_details_sync`` utility function after a Passkey is removed. This |
| 72 | +will ensure that the next time the user loads a page, their browser will be |
| 73 | +informed about the deleted Passkey and can remove it from storage. Sometimes |
| 74 | +this is associated with a prompt to the user to confirm the removal, depending |
| 75 | +on the browser's implementation. |
| 76 | + |
| 77 | +In the event that the user does try to use a removed Passkey during |
| 78 | +authentication, the browser will automatically be informed that the Passkey is |
| 79 | +no longer valid through the `PublicKeyCredential.signalUnknownCredential |
| 80 | +<https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/signalUnknownCredential_static>`_ |
| 81 | +WebAuthn API. |
| 82 | + |
| 83 | + |
| 84 | +How to trigger user details sync |
| 85 | +-------------------------------- |
| 86 | + |
| 87 | +To trigger the user details synchronization process, you need to call the |
| 88 | +``request_user_details_sync`` utility function whenever a user's details are |
| 89 | +updated. For example, if you have a view that allows users to update their email |
| 90 | +address or username, you would add a call to this function after the update is |
| 91 | +successful. This will ensure that the next time the user loads a page, their |
| 92 | +Passkeys will be updated with the new details. |
| 93 | + |
| 94 | +.. code-block:: py |
| 95 | +
|
| 96 | + # your_app/views.py |
| 97 | + from django_otp_webauthn.utils import request_user_details_sync |
| 98 | +
|
| 99 | + def update_user_details(request): |
| 100 | + if request.method == "POST": |
| 101 | + # Assume we have a form that updates user details |
| 102 | + form = UserDetailsForm(request.POST, instance=request.user) |
| 103 | + if form.is_valid(): |
| 104 | + form.save() |
| 105 | + # Request user details sync after updating details |
| 106 | + request_user_details_sync(request) |
| 107 | + # Redirect or render success response |
| 108 | + return redirect("profile") |
| 109 | + else: |
| 110 | + form = UserDetailsForm(instance=request.user) |
| 111 | + return render(request, "update_user_details.html", {"form": form}) |
| 112 | +
|
| 113 | +By following these steps, you can ensure that your users' Passkeys remain |
| 114 | +up to date with their latest details. |
| 115 | + |
| 116 | +You won't see any visible messages or indicators when the synchronization occurs, as |
| 117 | +it happens silently in the background. However, you can check your browser's |
| 118 | +console for any errors or logs related to the synchronization process if needed. |
| 119 | + |
| 120 | +How does this work from a technical perspective? |
| 121 | +------------------------------------------------ |
| 122 | + |
| 123 | +The ``render_otp_webauthn_sync_signals_scripts`` template tag is a convenience |
| 124 | +wrapper that ends up calling the |
| 125 | +``PublicKeyCredential.signalAllAcceptedCredentials`` and |
| 126 | +``PublicKeyCredential.signalCurrentUserDetails`` WebAuthn browser APIs. It |
| 127 | +automatically retrieves a list of currently registered credentials and the |
| 128 | +current user details in the format these APIs expect. |
| 129 | + |
| 130 | +For more information about these APIs, refer to the following resources: |
| 131 | + |
| 132 | +- `PublicKeyCredential.signalCurrentUserDetails <https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/signalCurrentUserDetails_static>`_ |
| 133 | +- `PublicKeyCredential.signalAllAcceptedCredentials <https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/signalAllAcceptedCredentials_static>`_ |
| 134 | + |
| 135 | +.. note:: |
| 136 | + |
| 137 | + As of November 2025, these APIs are still relatively new and don't enjoy |
| 138 | + broad support from all browsers. Please see `Web authentication signal |
| 139 | + methods on caniuse.com <https://caniuse.com/wf-webauthn-signals>`_ for the |
| 140 | + most up-to-date browser support information. |
0 commit comments