Skip to content

Commit da39aea

Browse files
committed
Add how-to documentation about using
`render_otp_webauthn_sync_signals_scripts`
1 parent 36789b4 commit da39aea

File tree

3 files changed

+158
-7
lines changed

3 files changed

+158
-7
lines changed

docs/how_to_guides/index.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ Here are what you will find in this section:
2525

2626
Learn how to configure WebAuthn to work across multiple domains. For example, if your main application runs on ``https://example.com`` and you have a localized version on ``https://example.co.uk``.
2727

28+
.. grid-item-card:: :ref:`Keeping Passkeys up-to-date with changing user details <keeping_passkeys_in_sync>`
29+
30+
Learn how to keep Passkey user details saved in users' browsers up-to-date when details like email or username change.
31+
2832
.. toctree::
2933
:maxdepth: 2
3034
:hidden:
@@ -33,3 +37,4 @@ Here are what you will find in this section:
3337
Customize helper class <customize_helper_class.rst>
3438
Customize models <customize_models.rst>
3539
Configure related origins <configure_related_origins.rst>
40+
Keeping Passkeys up-to-date with changing user details <keeping_passkeys_in_sync.rst>
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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.

docs/wordlist.txt

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
AbstractWebAuthnAttestation
22
AbstractWebAuthnCredential
3+
APIs
34
auth
45
authenticator
56
authenticator's
@@ -13,42 +14,47 @@ BeginCredentialRegistrationView
1314
biometric
1415
biometrics
1516
Caddy
17+
caniuse
1618
ccTLD
1719
CompleteCredentialAuthenticationView
1820
CompleteCredentialRegistrationView
1921
cryptographic
2022
django
21-
Frontend
2223
frontend
24+
Frontend
2325
Furo
2426
gettext
2527
github
26-
http
2728
htmlcov
29+
http
2830
HTTPS
2931
js
3032
JSON
3133
localhost
3234
OneToOneField
35+
otp
3336
OTP
3437
OTPMiddleware
35-
otp
3638
passwordless
3739
pradyunsg
3840
pre
39-
PyPI
41+
PublicKeyCredential
4042
py
43+
PyPI
4144
Quickstart
45+
residentKey
46+
reStructuredText
4247
rpID
4348
rpIDs
44-
reStructuredText
45-
residentKey
49+
signalAllAcceptedCredentials
50+
signalCurrentUserDetails
51+
signalUnknownCredential
4652
Stormbase
4753
subclasses
4854
subclassing
4955
untrusted
50-
WebAuthn
5156
webauthn
57+
WebAuthn
5258
WebAuthn's
5359
WebAuthnCredential
5460
WebAuthnUserHandle

0 commit comments

Comments
 (0)