auth0-server-python 1.0.0b13__tar.gz → 1.0.0b15__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/PKG-INFO +41 -3
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/README.md +40 -2
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/pyproject.toml +1 -1
- auth0_server_python-1.0.0b15/src/auth0_server_python/auth_schemes/client_assertion.py +63 -0
- auth0_server_python-1.0.0b15/src/auth0_server_python/auth_server/__init__.py +6 -0
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/auth_server/mfa_client.py +36 -6
- auth0_server_python-1.0.0b15/src/auth0_server_python/auth_server/passwordless_client.py +473 -0
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/auth_server/server_client.py +502 -43
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/auth_types/__init__.py +183 -4
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/error/__init__.py +86 -21
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/tests/test_mfa_client.py +97 -0
- auth0_server_python-1.0.0b15/src/auth0_server_python/tests/test_passwordless_client.py +1184 -0
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/tests/test_server_client.py +1401 -245
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/utils/helpers.py +27 -1
- auth0_server_python-1.0.0b13/src/auth0_server_python/auth_server/__init__.py +0 -5
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/LICENSE +0 -0
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/auth_schemes/__init__.py +0 -0
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/auth_schemes/bearer_auth.py +0 -0
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/auth_schemes/dpop_auth.py +0 -0
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/auth_server/my_account_client.py +0 -0
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/encryption/__init__.py +0 -0
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/encryption/encrypt.py +0 -0
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/store/__init__.py +0 -0
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/store/abstract.py +0 -0
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/telemetry.py +0 -0
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/tests/test_dpop_auth.py +0 -0
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/tests/test_my_account_client.py +0 -0
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/tests/test_telemetry.py +0 -0
- {auth0_server_python-1.0.0b13 → auth0_server_python-1.0.0b15}/src/auth0_server_python/utils/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: auth0-server-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.0b15
|
|
4
4
|
Summary: Auth0 server-side Python SDK
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -48,7 +48,7 @@ pip install auth0-server-python
|
|
|
48
48
|
If you’re using Poetry:
|
|
49
49
|
|
|
50
50
|
```shell
|
|
51
|
-
poetry
|
|
51
|
+
poetry add auth0-server-python
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
### 2. Create the Auth0 SDK client
|
|
@@ -65,7 +65,7 @@ auth0 = ServerClient(
|
|
|
65
65
|
client_secret='<AUTH0_CLIENT_SECRET>',
|
|
66
66
|
secret='<AUTH0_SECRET>',
|
|
67
67
|
authorization_params= {
|
|
68
|
-
redirect_uri: '<AUTH0_REDIRECT_URI>',
|
|
68
|
+
'redirect_uri': '<AUTH0_REDIRECT_URI>',
|
|
69
69
|
}
|
|
70
70
|
)
|
|
71
71
|
```
|
|
@@ -80,6 +80,35 @@ The `AUTH0_SECRET` is the key used to encrypt the session and transaction cookie
|
|
|
80
80
|
openssl rand -hex 64
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
+
#### Authenticating with Private Key JWT
|
|
84
|
+
|
|
85
|
+
The SDK authenticates to Auth0 with either a client secret or a Private Key JWT (`private_key_jwt`). To use Private Key JWT, pass your private signing key as `client_assertion_signing_key` instead of `client_secret`:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from auth0_server_python.auth_server.server_client import ServerClient
|
|
89
|
+
|
|
90
|
+
with open('private_key.pem') as f:
|
|
91
|
+
private_key = f.read()
|
|
92
|
+
|
|
93
|
+
auth0 = ServerClient(
|
|
94
|
+
domain='<AUTH0_DOMAIN>',
|
|
95
|
+
client_id='<AUTH0_CLIENT_ID>',
|
|
96
|
+
client_assertion_signing_key=private_key,
|
|
97
|
+
secret='<AUTH0_SECRET>',
|
|
98
|
+
authorization_params={
|
|
99
|
+
'redirect_uri': '<AUTH0_REDIRECT_URI>',
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The key must be a PKCS8 PEM private key. Register its public key on your Auth0 application under **Settings → Credentials**, and set the application's authentication method to Private Key JWT. The signing algorithm defaults to `RS256` and can be overridden with `client_assertion_signing_alg`. Auth0 accepts `RS256`, `RS384`, and `PS256`, all of which use an RSA key. The algorithm must match the key type and the algorithm chosen when the public key credential was created.
|
|
105
|
+
|
|
106
|
+
> [!NOTE]
|
|
107
|
+
> The passkey challenge endpoints (`register` and `challenge`) accept only a client secret, so a client configured with just a signing key cannot use them.
|
|
108
|
+
|
|
109
|
+
> [!IMPORTANT]
|
|
110
|
+
> Private keys must not be committed to source control. Load them from a secure secret store or an environment-provided file.
|
|
111
|
+
|
|
83
112
|
### 3. Add login to your Application (interactive)
|
|
84
113
|
|
|
85
114
|
Before using redirect-based login, ensure the `redirect_uri` is configured when initializing the SDK:
|
|
@@ -172,6 +201,11 @@ response = await auth0.custom_token_exchange(
|
|
|
172
201
|
print(response.access_token)
|
|
173
202
|
```
|
|
174
203
|
|
|
204
|
+
Building on token exchange, the SDK also supports:
|
|
205
|
+
|
|
206
|
+
- **[Delegation and Impersonation](examples/CustomTokenExchange.md#3-actor-tokens-delegation)** - exchange with an `actor_token` so the issued tokens record who is acting on whose behalf (the `act` claim).
|
|
207
|
+
- **[Impersonation via Session Transfer (STT)](examples/CustomTokenExchange.md#8-impersonation-via-session-transfer-stt)** - mint a Session Transfer Token to log an agent into a target app as a customer, via `request_session_transfer_token()` and `build_session_transfer_redirect()`.
|
|
208
|
+
|
|
175
209
|
For more details and examples, see [examples/CustomTokenExchange.md](examples/CustomTokenExchange.md).
|
|
176
210
|
|
|
177
211
|
### 5. Multiple Custom Domains (MCD)
|
|
@@ -220,6 +254,10 @@ Let a logged-in user manage their own enrolled authentication methods — enroll
|
|
|
220
254
|
|
|
221
255
|
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).
|
|
222
256
|
|
|
257
|
+
### 10. Passwordless Authentication
|
|
258
|
+
|
|
259
|
+
Sign users in with a one-time code sent by email or SMS, or with a magic link sent by email, via [Auth0 embedded passwordless login](https://auth0.com/docs/authenticate/passwordless/implement-login/embedded-login/relevant-api-endpoints). OTP verification and the magic-link callback each establish a server-side session like every other login path. For prerequisites, both flows, custom scopes/audiences, step-up MFA, and error handling, see [examples/Passwordless.md](examples/Passwordless.md).
|
|
260
|
+
|
|
223
261
|
## Feedback
|
|
224
262
|
|
|
225
263
|
### Contributing
|
|
@@ -23,7 +23,7 @@ pip install auth0-server-python
|
|
|
23
23
|
If you’re using Poetry:
|
|
24
24
|
|
|
25
25
|
```shell
|
|
26
|
-
poetry
|
|
26
|
+
poetry add auth0-server-python
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
### 2. Create the Auth0 SDK client
|
|
@@ -40,7 +40,7 @@ auth0 = ServerClient(
|
|
|
40
40
|
client_secret='<AUTH0_CLIENT_SECRET>',
|
|
41
41
|
secret='<AUTH0_SECRET>',
|
|
42
42
|
authorization_params= {
|
|
43
|
-
redirect_uri: '<AUTH0_REDIRECT_URI>',
|
|
43
|
+
'redirect_uri': '<AUTH0_REDIRECT_URI>',
|
|
44
44
|
}
|
|
45
45
|
)
|
|
46
46
|
```
|
|
@@ -55,6 +55,35 @@ The `AUTH0_SECRET` is the key used to encrypt the session and transaction cookie
|
|
|
55
55
|
openssl rand -hex 64
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
#### Authenticating with Private Key JWT
|
|
59
|
+
|
|
60
|
+
The SDK authenticates to Auth0 with either a client secret or a Private Key JWT (`private_key_jwt`). To use Private Key JWT, pass your private signing key as `client_assertion_signing_key` instead of `client_secret`:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from auth0_server_python.auth_server.server_client import ServerClient
|
|
64
|
+
|
|
65
|
+
with open('private_key.pem') as f:
|
|
66
|
+
private_key = f.read()
|
|
67
|
+
|
|
68
|
+
auth0 = ServerClient(
|
|
69
|
+
domain='<AUTH0_DOMAIN>',
|
|
70
|
+
client_id='<AUTH0_CLIENT_ID>',
|
|
71
|
+
client_assertion_signing_key=private_key,
|
|
72
|
+
secret='<AUTH0_SECRET>',
|
|
73
|
+
authorization_params={
|
|
74
|
+
'redirect_uri': '<AUTH0_REDIRECT_URI>',
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The key must be a PKCS8 PEM private key. Register its public key on your Auth0 application under **Settings → Credentials**, and set the application's authentication method to Private Key JWT. The signing algorithm defaults to `RS256` and can be overridden with `client_assertion_signing_alg`. Auth0 accepts `RS256`, `RS384`, and `PS256`, all of which use an RSA key. The algorithm must match the key type and the algorithm chosen when the public key credential was created.
|
|
80
|
+
|
|
81
|
+
> [!NOTE]
|
|
82
|
+
> The passkey challenge endpoints (`register` and `challenge`) accept only a client secret, so a client configured with just a signing key cannot use them.
|
|
83
|
+
|
|
84
|
+
> [!IMPORTANT]
|
|
85
|
+
> Private keys must not be committed to source control. Load them from a secure secret store or an environment-provided file.
|
|
86
|
+
|
|
58
87
|
### 3. Add login to your Application (interactive)
|
|
59
88
|
|
|
60
89
|
Before using redirect-based login, ensure the `redirect_uri` is configured when initializing the SDK:
|
|
@@ -147,6 +176,11 @@ response = await auth0.custom_token_exchange(
|
|
|
147
176
|
print(response.access_token)
|
|
148
177
|
```
|
|
149
178
|
|
|
179
|
+
Building on token exchange, the SDK also supports:
|
|
180
|
+
|
|
181
|
+
- **[Delegation and Impersonation](examples/CustomTokenExchange.md#3-actor-tokens-delegation)** - exchange with an `actor_token` so the issued tokens record who is acting on whose behalf (the `act` claim).
|
|
182
|
+
- **[Impersonation via Session Transfer (STT)](examples/CustomTokenExchange.md#8-impersonation-via-session-transfer-stt)** - mint a Session Transfer Token to log an agent into a target app as a customer, via `request_session_transfer_token()` and `build_session_transfer_redirect()`.
|
|
183
|
+
|
|
150
184
|
For more details and examples, see [examples/CustomTokenExchange.md](examples/CustomTokenExchange.md).
|
|
151
185
|
|
|
152
186
|
### 5. Multiple Custom Domains (MCD)
|
|
@@ -195,6 +229,10 @@ Let a logged-in user manage their own enrolled authentication methods — enroll
|
|
|
195
229
|
|
|
196
230
|
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).
|
|
197
231
|
|
|
232
|
+
### 10. Passwordless Authentication
|
|
233
|
+
|
|
234
|
+
Sign users in with a one-time code sent by email or SMS, or with a magic link sent by email, via [Auth0 embedded passwordless login](https://auth0.com/docs/authenticate/passwordless/implement-login/embedded-login/relevant-api-endpoints). OTP verification and the magic-link callback each establish a server-side session like every other login path. For prerequisites, both flows, custom scopes/audiences, step-up MFA, and error handling, see [examples/Passwordless.md](examples/Passwordless.md).
|
|
235
|
+
|
|
198
236
|
## Feedback
|
|
199
237
|
|
|
200
238
|
### Contributing
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import secrets
|
|
2
|
+
import time
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
import jwt
|
|
6
|
+
|
|
7
|
+
from auth0_server_python.error import ConfigurationError
|
|
8
|
+
|
|
9
|
+
# RFC 7523 client-assertion type for private_key_jwt authentication.
|
|
10
|
+
CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
|
|
11
|
+
|
|
12
|
+
# Assertion lifetime in seconds.
|
|
13
|
+
_ASSERTION_LIFETIME = 60
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def validate_client_assertion_key(private_key: Union[str, bytes], alg: str = "RS256") -> None:
|
|
17
|
+
"""
|
|
18
|
+
Verify the signing key and algorithm can produce a client assertion.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
private_key: The client's private signing key (PKCS8 PEM string or bytes).
|
|
22
|
+
alg: The signing algorithm (defaults to "RS256").
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
ConfigurationError: If the key is malformed or does not match the algorithm.
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
jwt.encode({"probe": True}, private_key, algorithm=alg)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
raise ConfigurationError(
|
|
31
|
+
f"Invalid client_assertion_signing_key for algorithm {alg}: {e}"
|
|
32
|
+
) from e
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def build_client_assertion(
|
|
36
|
+
private_key: Union[str, bytes],
|
|
37
|
+
client_id: str,
|
|
38
|
+
issuer: str,
|
|
39
|
+
alg: str = "RS256",
|
|
40
|
+
) -> str:
|
|
41
|
+
"""
|
|
42
|
+
Mint a private_key_jwt client assertion for token-endpoint authentication (RFC 7523).
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
private_key: The client's private signing key (PKCS8 PEM string or bytes).
|
|
46
|
+
client_id: The Auth0 client ID, used as both the issuer and subject claim.
|
|
47
|
+
issuer: The authorization server issuer identifier, used as the audience claim.
|
|
48
|
+
alg: The signing algorithm (defaults to "RS256").
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
The signed client-assertion JWT.
|
|
52
|
+
"""
|
|
53
|
+
now = int(time.time())
|
|
54
|
+
payload = {
|
|
55
|
+
"iss": client_id,
|
|
56
|
+
"sub": client_id,
|
|
57
|
+
"aud": issuer,
|
|
58
|
+
"iat": now,
|
|
59
|
+
"nbf": now,
|
|
60
|
+
"exp": now + _ASSERTION_LIFETIME,
|
|
61
|
+
"jti": secrets.token_urlsafe(32),
|
|
62
|
+
}
|
|
63
|
+
return jwt.encode(payload, private_key, algorithm=alg)
|
|
@@ -5,7 +5,8 @@ Handles Multi-Factor Authentication operations against the Auth0 MFA API.
|
|
|
5
5
|
|
|
6
6
|
import json
|
|
7
7
|
import time
|
|
8
|
-
from
|
|
8
|
+
from collections.abc import Awaitable, Callable
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
9
10
|
|
|
10
11
|
import httpx
|
|
11
12
|
|
|
@@ -28,6 +29,7 @@ from auth0_server_python.auth_types import (
|
|
|
28
29
|
)
|
|
29
30
|
from auth0_server_python.encryption.encrypt import decrypt, encrypt
|
|
30
31
|
from auth0_server_python.error import (
|
|
32
|
+
ConfigurationError,
|
|
31
33
|
DomainResolverError,
|
|
32
34
|
MfaChallengeError,
|
|
33
35
|
MfaEnrollmentError,
|
|
@@ -66,7 +68,12 @@ class MfaClient:
|
|
|
66
68
|
secret: str,
|
|
67
69
|
state_store=None,
|
|
68
70
|
state_identifier: str = "_a0_session",
|
|
69
|
-
headers: Optional[dict[str, str]] = None
|
|
71
|
+
headers: Optional[dict[str, str]] = None,
|
|
72
|
+
session_establisher: Optional[
|
|
73
|
+
Callable[..., Awaitable[None]]
|
|
74
|
+
] = None,
|
|
75
|
+
mfa_token_ttl: int = DEFAULT_MFA_TOKEN_TTL,
|
|
76
|
+
apply_client_authentication: Optional[Callable] = None,
|
|
70
77
|
):
|
|
71
78
|
if callable(domain):
|
|
72
79
|
self._domain = None
|
|
@@ -80,12 +87,24 @@ class MfaClient:
|
|
|
80
87
|
self._state_store = state_store
|
|
81
88
|
self._state_identifier = state_identifier
|
|
82
89
|
self._headers = headers or {}
|
|
90
|
+
self._session_establisher = session_establisher
|
|
91
|
+
if mfa_token_ttl <= 0:
|
|
92
|
+
raise ConfigurationError("mfa_token_ttl must be a positive number of seconds")
|
|
93
|
+
self._mfa_token_ttl = mfa_token_ttl
|
|
94
|
+
self._apply_client_authentication = apply_client_authentication
|
|
83
95
|
|
|
84
96
|
def _get_http_client(self, **kwargs) -> httpx.AsyncClient:
|
|
85
97
|
"""Return an httpx.AsyncClient with default headers injected."""
|
|
86
98
|
headers = {**kwargs.pop("headers", {}), **self._headers}
|
|
87
99
|
return httpx.AsyncClient(headers=headers, **kwargs)
|
|
88
100
|
|
|
101
|
+
def _apply_mfa_client_authentication(self, body: dict, base_url: str) -> None:
|
|
102
|
+
"""Add client authentication to an MFA request body (client_secret or client assertion)."""
|
|
103
|
+
if self._apply_client_authentication:
|
|
104
|
+
self._apply_client_authentication(body, f"{base_url}/", in_body=True)
|
|
105
|
+
elif self._client_secret:
|
|
106
|
+
body["client_secret"] = self._client_secret
|
|
107
|
+
|
|
89
108
|
async def _resolve_base_url(
|
|
90
109
|
self,
|
|
91
110
|
store_options: Optional[dict[str, Any]] = None
|
|
@@ -137,7 +156,7 @@ class MfaClient:
|
|
|
137
156
|
raise MfaTokenInvalidError()
|
|
138
157
|
|
|
139
158
|
elapsed = int(time.time()) - context.created_at
|
|
140
|
-
if elapsed >
|
|
159
|
+
if elapsed > self._mfa_token_ttl:
|
|
141
160
|
raise MfaTokenExpiredError()
|
|
142
161
|
|
|
143
162
|
return context
|
|
@@ -393,6 +412,7 @@ class MfaClient:
|
|
|
393
412
|
|
|
394
413
|
Raises:
|
|
395
414
|
MfaChallengeError: When the challenge fails.
|
|
415
|
+
ConfigurationError: If neither client_secret nor client_assertion_signing_key is configured.
|
|
396
416
|
"""
|
|
397
417
|
mfa_token = options.get("mfa_token")
|
|
398
418
|
if not mfa_token:
|
|
@@ -415,9 +435,9 @@ class MfaClient:
|
|
|
415
435
|
body: dict[str, Any] = {
|
|
416
436
|
"mfa_token": context.mfa_token,
|
|
417
437
|
"client_id": self._client_id,
|
|
418
|
-
"client_secret": self._client_secret,
|
|
419
438
|
"challenge_type": challenge_type
|
|
420
439
|
}
|
|
440
|
+
self._apply_mfa_client_authentication(body, base_url)
|
|
421
441
|
|
|
422
442
|
if "authenticator_id" in options and options["authenticator_id"]:
|
|
423
443
|
body["authenticator_id"] = options["authenticator_id"]
|
|
@@ -482,17 +502,20 @@ class MfaClient:
|
|
|
482
502
|
MfaVerifyError: When verification fails, or when dpop_key was supplied
|
|
483
503
|
but the server returned an unbound (Bearer) token.
|
|
484
504
|
MfaRequiredError: When chained MFA is required.
|
|
505
|
+
ConfigurationError: If neither client_secret nor client_assertion_signing_key is configured.
|
|
485
506
|
"""
|
|
486
507
|
mfa_token = options.get("mfa_token")
|
|
487
508
|
if not mfa_token:
|
|
488
509
|
raise MfaTokenInvalidError()
|
|
489
510
|
context = self.decrypt_mfa_token(mfa_token)
|
|
490
511
|
|
|
512
|
+
base_url = await self._resolve_base_url(store_options)
|
|
513
|
+
|
|
491
514
|
body: dict[str, Any] = {
|
|
492
515
|
"client_id": self._client_id,
|
|
493
|
-
"client_secret": self._client_secret,
|
|
494
516
|
"mfa_token": context.mfa_token
|
|
495
517
|
}
|
|
518
|
+
self._apply_mfa_client_authentication(body, base_url)
|
|
496
519
|
|
|
497
520
|
if "otp" in options:
|
|
498
521
|
body["grant_type"] = "http://auth0.com/oauth/grant-type/mfa-otp"
|
|
@@ -511,7 +534,6 @@ class MfaClient:
|
|
|
511
534
|
)
|
|
512
535
|
|
|
513
536
|
try:
|
|
514
|
-
base_url = await self._resolve_base_url(store_options)
|
|
515
537
|
token_endpoint = f"{base_url}/oauth/token"
|
|
516
538
|
|
|
517
539
|
async with self._get_http_client() as client:
|
|
@@ -626,6 +648,14 @@ class MfaClient:
|
|
|
626
648
|
)
|
|
627
649
|
|
|
628
650
|
if not state_data:
|
|
651
|
+
if self._session_establisher:
|
|
652
|
+
await self._session_establisher(
|
|
653
|
+
verify_response=verify_response,
|
|
654
|
+
audience=audience,
|
|
655
|
+
scope=scope,
|
|
656
|
+
store_options=store_options,
|
|
657
|
+
)
|
|
658
|
+
return
|
|
629
659
|
raise MfaVerifyError(
|
|
630
660
|
"No existing session found to update with MFA tokens"
|
|
631
661
|
)
|