You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
-4Lines changed: 0 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -191,10 +191,6 @@ Sign users up or in with [WebAuthn](https://www.w3.org/TR/webauthn-2/) passkeys
191
191
192
192
Let a logged-in user manage their own enrolled authentication methods — enroll a new passkey (or other factor), list, rename, and delete — via the [My Account API](https://auth0.com/docs/manage-users/my-account-api). For obtaining a scoped token, the enroll/verify ceremony, listing, updating, deleting, and error handling, see [examples/MyAccountAuthenticationMethods.md](examples/MyAccountAuthenticationMethods.md).
Bind tokens to a key your server holds ([RFC 9449](https://www.rfc-editor.org/rfc/rfc9449)) so a stolen token alone cannot be replayed. DPoP is supported for Passkey sign-in (`signin_with_passkey`) and the authentication-methods/factors methods on `MyAccountClient`. For key generation and usage, see [examples/Passkeys.md](examples/Passkeys.md#3-dpop-bound-passkey-tokens-optional) and [examples/MyAccountAuthenticationMethods.md](examples/MyAccountAuthenticationMethods.md#dpop).
> When polling for push notification approval, the API returns an `authorization_pending` error until the user approves or denies the request. A `slow_down` error indicates you should increase the polling interval.
522
521
523
-
### Verify with DPoP (sender-constrained tokens)
524
-
525
-
When the login that triggered MFA was DPoP-bound (for example a `signin_with_passkey(dpop_key=...)` that returned `MfaRequiredError`), pass the **same**`dpop_key` to `verify()` so the token minted by the MFA step-up stays sender-constrained:
526
-
527
-
```python
528
-
verify_response =await server_client.mfa.verify(
529
-
{
530
-
"mfa_token": mfa_token,
531
-
"otp": "123456",
532
-
},
533
-
dpop_key=dpop_key, # the same EC P-256 key the login was bound to
534
-
)
535
-
536
-
assert verify_response.token_type =="DPoP"
537
-
```
538
-
539
-
The SDK does not store your private key, so you must re-supply it on the `verify()` call. It attaches a token-endpoint DPoP proof, transparently handles the server-nonce challenge, and **rejects a Bearer downgrade** — if `dpop_key` was supplied but the server returned an unbound token (or vice versa), `verify()` raises `MfaVerifyError` instead of silently dropping the sender constraint.
540
-
541
-
> [!WARNING]
542
-
> The `dpop_key` is a **Tier 0 secret**. Keep it in your secret store, never log it, use one key per user/session, and use **EC P-256 only**.
543
-
544
522
## Session Persistence
545
523
546
524
By default, `verify()` returns tokens without persisting them to the session store. However, you can automatically persist tokens by setting `persist=True`.
Copy file name to clipboardExpand all lines: examples/MyAccountAuthenticationMethods.md
+1-20Lines changed: 1 addition & 20 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,7 +6,7 @@ The [My Account API](https://auth0.com/docs/manage-users/my-account-api) lets a
6
6
> This is a different My Account resource from [Connected Accounts](ConnectedAccounts.md) (Token Vault). Connected-accounts management is exposed as convenience methods on `ServerClient`; **authentication-method management is on `MyAccountClient` directly**, because each call takes a user access token you obtain yourself. The two share the same My Account setup (activation, MRRT, scopes, `MyAccountApiError`) — see [ConnectedAccounts.md → Pre-requisites](ConnectedAccounts.md#pre-requisites) for that common setup.
7
7
8
8
> [!NOTE]
9
-
> To **sign in** with a passkey (rather than manage one), see [examples/Passkeys.md](Passkeys.md). To **bind these calls to a held key**, pass an optional `dpop_key` — see [DPoP](#dpop) below.
9
+
> To **sign in** with a passkey (rather than manage one), see [examples/Passkeys.md](Passkeys.md).
10
10
11
11
## Table of Contents
12
12
@@ -18,7 +18,6 @@ The [My Account API](https://auth0.com/docs/manage-users/my-account-api) lets a
18
18
-[4. Get a single authentication method](#4-get-a-single-authentication-method)
19
19
-[5. Update (rename) an authentication method](#5-update-rename-an-authentication-method)
20
20
-[6. Delete an authentication method](#6-delete-an-authentication-method)
Every method above accepts an optional `dpop_key` to present a sender-constrained token (`Authorization: DPoP` + a per-request proof) instead of a Bearer token ([RFC 9449](https://www.rfc-editor.org/rfc/rfc9449)). Pass the **same key** the access token was bound to at sign-in:
181
-
182
-
```python
183
-
from jwcrypto import jwk
184
-
185
-
dpop_key = jwk.JWK.generate(kty="EC", crv="P-256") # the key the token was bound to
> The `dpop_key` private key is a **Tier 0 secret**. Keep it in your secret store (KMS/HSM), never log it (`repr()` is redacted, but `key.export_private()` is not), use **one key per user/session** (never share across principals), and use **EC P-256 only** — any other key type fails closed with a `ValueError`.
195
-
196
177
## Error Handling
197
178
198
179
All errors inherit from `Auth0Error`. My Account API errors are `MyAccountApiError` (RFC 7807 problem-details, carrying `status`, `detail`, and optional `validation_errors`); missing arguments raise `MissingRequiredArgumentError`; transport or non-JSON responses surface as `ApiError`.
-[Completing MFA on a passkey login (and where the session comes from)](#completing-mfa-on-a-passkey-login-and-where-the-session-comes-from)
19
18
-[Error Handling](#error-handling)
20
19
@@ -136,31 +135,6 @@ result = await server_client.signin_with_passkey(
136
135
> [!NOTE]
137
136
> The SDK is transparent to the signup-vs-login difference in the credential `response` — both flow through the same `PasskeyAuthResponse.response` dict. Send exactly the keys the browser produced.
138
137
139
-
## 3. DPoP-bound passkey tokens (optional)
140
-
141
-
Pass an optional `dpop_key` to bind the issued tokens to a key your server holds ([RFC 9449](https://www.rfc-editor.org/rfc/rfc9449)), so a stolen token alone cannot be replayed. DPoP is **opt-in**: omit `dpop_key` and sign-in returns ordinary Bearer tokens with no behaviour change.
142
-
143
-
```python
144
-
from jwcrypto import jwk
145
-
146
-
dpop_key = jwk.JWK.generate(kty="EC", crv="P-256") # you generate and keep this key (Tier 0)
When `dpop_key` is supplied, the SDK attaches a token-endpoint proof so Auth0 issues a DPoP-bound token, transparently handles the server-nonce challenge, and **rejects a Bearer downgrade** — if the server returns an unbound token, `signin_with_passkey` raises `PasskeyError` rather than silently accepting a token bound to a key it never used.
157
-
158
-
> [!TIP]
159
-
> Reuse the **same**`dpop_key` for any subsequent My Account API calls made with the resulting token — the token is bound to that one key. See [MyAccountAuthenticationMethods.md → DPoP](MyAccountAuthenticationMethods.md#dpop).
160
-
161
-
> [!WARNING]
162
-
> The `dpop_key` private key is a **Tier 0 secret**. Keep it in your secret store (KMS/HSM), never log it (`repr()` is redacted, but `key.export_private()` is not), use **one key per user/session** (never share across principals), and use **EC P-256 only** — any other key type fails closed with a `ValueError` before any network call.
163
-
164
138
### Completing MFA on a passkey login (and where the session comes from)
165
139
166
140
When a passkey login needs a second factor, `signin_with_passkey` raises `MfaRequiredError`**before** it creates a session. You finish the login by challenging and verifying through `client.mfa`, then **store the returned tokens yourself** — on this path the SDK does not persist the session for you (`persist` defaults to `False`, and there is no existing session to update yet):
@@ -172,7 +146,6 @@ try:
172
146
result =await server_client.signin_with_passkey(
173
147
auth_session=auth_session,
174
148
authn_response=authn_response,
175
-
dpop_key=dpop_key, # optional; omit for Bearer tokens
> Re-supply the **same**`dpop_key` to `verify`. Omitting it when the login was DPoP-bound would downgrade the result to a Bearer token; `verify`**rejects** that mismatch with `MfaVerifyError` rather than silently dropping the sender constraint. DPoP is preserved end to end — `persist=False` affects only *who writes the session*, never the token binding.
208
-
209
177
> [!NOTE]
210
178
> Do **not** pass `persist=True` on this path. It updates an *existing* session, and a passkey-first login has none yet, so it raises `MfaVerifyError("No existing session found…")` — discarding the tokens `verify` just obtained. Use `persist=False` and store the returned tokens as shown above. (This is pre-existing MFA-client behavior, unrelated to passkeys.)
211
179
@@ -252,7 +220,7 @@ except Auth0Error as e:
252
220
### Common error codes (`PasskeyErrorCode`)
253
221
254
222
-`passkey_challenge_error` — the signup/login challenge request failed
255
-
-`passkey_token_error` — token exchange failed (also used for a rejected DPoP downgrade)
223
+
-`passkey_token_error` — token exchange failed
256
224
-`invalid_response` — Auth0 returned a response that could not be parsed
0 commit comments