Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/ConnectedAccounts.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ server_client = ServerClient(
)
```

`ServerClient` creates no default stores. Provide a `state_store` and a `transaction_store`. See [ConfigureStore.md](ConfigureStore.md).

## Login to the application

Use the login methods to authenticate to the application and get a refresh token in order to use MRRT to call the My Account API. If you are also intending to authorize access to any custom API, you may optionally specify an audience and any relevant scopes but you must at a minimum include the `offline_access` scope to ensure you obtain a refresh token.
Expand Down
2 changes: 2 additions & 0 deletions examples/CustomTokenExchange.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ if response.id_token:
print(f"ID Token: {response.id_token}")
```

`ServerClient` creates no default stores. Provide a `state_store` and a `transaction_store`. See [ConfigureStore.md](ConfigureStore.md).

## 2. Login with Token Exchange

Exchange a custom token AND establish a user session.
Expand Down
3 changes: 3 additions & 0 deletions examples/InteractiveLogin.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ server_client = ServerClient(
}
)
```

`ServerClient` creates no default stores. Provide a `state_store` and a `transaction_store`. See [ConfigureStore.md](ConfigureStore.md).

Now call `start_interactive_login()` to obtain the authorization URL and redirect the user:
```python
authorization_url = await server_client.start_interactive_login()
Expand Down
2 changes: 2 additions & 0 deletions examples/MFA.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ except MfaRequiredError as error:
mfa_token = context.mfa_token # Raw token for MFA API calls
```

`ServerClient` creates no default stores. Provide a `state_store` and a `transaction_store`. See [ConfigureStore.md](ConfigureStore.md).

> [!NOTE]
> `get_access_token()` is not the only origin of `MfaRequiredError`. A passkey login (`signin_with_passkey`) raises the same error when a second factor is required — but there is **no session yet** at that point, which changes how you complete and persist the flow. See [Passkeys.md → Completing MFA on a passkey login](Passkeys.md#completing-mfa-on-a-passkey-login-and-where-the-session-comes-from).

Expand Down
2 changes: 2 additions & 0 deletions examples/MultipleCustomDomains.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ client = ServerClient(
)
```

`ServerClient` creates no default stores. Provide a `state_store` and a `transaction_store`. See [ConfigureStore.md](ConfigureStore.md).

### Method 2: Dynamic Domain Resolver (MCD)

For MCD support, provide a domain resolver function that receives a `DomainResolverContext`:
Expand Down
2 changes: 2 additions & 0 deletions examples/MutualTLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ auth0 = ServerClient(
)
```

`ServerClient` creates no default stores. Provide a `state_store` and a `transaction_store`. See [ConfigureStore.md](ConfigureStore.md).

The SDK passes `ssl_context` as `verify=ssl_context` to every `httpx.AsyncClient` it constructs, including the authlib client used for the authorization-code exchange. You never call `load_cert_chain` inside the SDK - the caller owns the TLS material.

## Mutual exclusion
Expand Down
2 changes: 2 additions & 0 deletions examples/OrganizationLogin.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ authorization_url = await auth0.start_interactive_login(
)
```

`ServerClient` creates no default stores. Provide a `state_store` and a `transaction_store`. See [ConfigureStore.md](ConfigureStore.md).

`organization` accepts either an org ID (with the `org_` prefix) or a human-readable org name. The SDK validates the corresponding `org_id` or `org_name` claim in the returned ID token.

## Multi-Org Login
Expand Down
15 changes: 13 additions & 2 deletions examples/Passkeys.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,26 @@ A passkey ceremony is always **two steps**, because the WebAuthn signature happe
```python
from auth0_server_python.auth_server.server_client import ServerClient

# Passkey sign-in persists a server-side session, and the challenge step stores
# short-lived transaction data, so ServerClient needs a state store and a
# transaction store (it does not create defaults). If your framework wrapper
# (e.g. auth0-fastapi) already provides these, reuse them. Otherwise, create
# your own implementations of the StateStore / TransactionStore ABCs.
state_store = YourStateStore(...) # or the store your framework provides
transaction_store = YourTransactionStore(...) # or the store your framework provides

server_client = ServerClient(
domain="YOUR_CUSTOM_DOMAIN",
client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET",
secret="YOUR_SECRET",
state_store=state_store,
transaction_store=transaction_store,
)
```

For store implementations (cookie, Redis, database) and how to pass `store_options`, see [examples/ConfigureStore.md](ConfigureStore.md).

The **Passkey** grant (`urn:okta:params:oauth:grant-type:webauthn`) must be enabled for your application under **Applications → Your App → Grant Types**.

> [!NOTE]
Expand Down Expand Up @@ -105,12 +117,11 @@ print(f"Signed up and logged in: {user['sub']}")

## 2. Passkey Login

Identical shape, different endpoints. The login challenge takes an optional `username` hint (for conditional UI), and the browser uses `navigator.credentials.get()`.
Identical shape, different endpoints. The browser uses `navigator.credentials.get()` and the user picks a passkey from the prompt. That credential identifies the user, so the login challenge takes no username.

```python
# Step 1 — login challenge
challenge = await server_client.passkey_login_challenge(
username="existing.user@example.com", # optional
connection="Username-Password-Authentication", # optional
store_options={"request": request, "response": response},
)
Expand Down
2 changes: 2 additions & 0 deletions examples/Passwordless.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ server_client = ServerClient(
)
```

`ServerClient` creates no default stores. Provide a `state_store` and a `transaction_store`. See [ConfigureStore.md](ConfigureStore.md).

For apps using request/response-backed stores or multiple custom domains, pass `store_options={"request": request, "response": response}` to each method that reads or writes transaction/session state.

## 1. Email OTP
Expand Down
2 changes: 2 additions & 0 deletions examples/StepUpAuthentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ async def handle_callback(callback_url, request, response):
return (result.get("app_state") or {}).get("returnTo", "/")
```

`ServerClient` creates no default stores. Provide a `state_store` and a `transaction_store`. See [ConfigureStore.md](ConfigureStore.md).

> [!NOTE]
> The redirect itself is framework-specific - these handlers return the URL to redirect to, and your app issues the actual HTTP redirect (e.g. a `302`/`303`). `max_age: 0` matters: without it, a user who authenticated moments ago may be returned straight to your callback without a fresh MFA prompt.

Expand Down
17 changes: 14 additions & 3 deletions src/auth0_server_python/auth_server/server_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3520,7 +3520,11 @@ async def passkey_login_challenge(
then call signin_with_passkey() with the auth_session and credential result.

Args:
username: Optional username hint for conditional UI.
username: Deprecated and ignored. Auth0 rejects ``username`` on the
passkey login challenge (the user is identified by the selected
credential, not by a supplied username), so it is never forwarded.
Retained for backward compatibility and will be removed in a future
major release. Passing it emits a DeprecationWarning.
connection: Auth0 database connection name (realm).
organization: Auth0 organization ID or name.
store_options: Optional options for domain resolution.
Expand All @@ -3532,15 +3536,22 @@ async def passkey_login_challenge(
PasskeyError: If the challenge request fails.
EnterpriseConnectError: If the client is configured for Enterprise Connect.
"""
if username is not None:
warnings.warn(
"The 'username' argument to passkey_login_challenge is ignored: Auth0 "
"rejects 'username' on the passkey login challenge (the user is "
"identified by the selected credential). It is retained for backward "
"compatibility and will be removed in a future major release.",
DeprecationWarning,
stacklevel=2,
)
try:
domain = await self._resolve_current_domain(store_options)

resolved_org = organization or self._organization
body: dict[str, Any] = {"client_id": self._client_id}
if self._client_secret:
body["client_secret"] = self._client_secret
if username:
body["username"] = username
if connection:
body["realm"] = connection
if resolved_org:
Expand Down
10 changes: 7 additions & 3 deletions src/auth0_server_python/tests/test_server_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7819,7 +7819,10 @@ async def test_passkey_login_challenge_minimal_body(mocker):


@pytest.mark.asyncio
async def test_passkey_login_challenge_with_username(mocker):
async def test_passkey_login_challenge_ignores_username(mocker):
"""username is deprecated and never forwarded: Auth0 rejects it on the login
challenge (passkey login is discoverable/usernameless). Passing it emits a
DeprecationWarning and leaves the request body unchanged."""
client = ServerClient(
domain="auth0.local",
client_id="test_client_id",
Expand All @@ -7834,11 +7837,12 @@ async def test_passkey_login_challenge_with_username(mocker):
mock_response.json = MagicMock(return_value=_PASSKEY_LOGIN_CHALLENGE_RESPONSE)
mock_post.return_value = mock_response

await client.passkey_login_challenge(username="jane@example.com")
with pytest.warns(DeprecationWarning):
await client.passkey_login_challenge(username="jane@example.com")

args, kwargs = mock_post.call_args
body = kwargs["json"]
assert body["username"] == "jane@example.com"
assert "username" not in body


@pytest.mark.asyncio
Expand Down
Loading