auth0-server-python 1.0.0b10__tar.gz → 1.0.0b11__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.0b10 → auth0_server_python-1.0.0b11}/PKG-INFO +5 -1
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/README.md +4 -0
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/pyproject.toml +1 -1
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/auth_server/server_client.py +104 -5
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/auth_types/__init__.py +12 -41
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/error/__init__.py +13 -0
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/tests/test_server_client.py +1342 -35
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/utils/helpers.py +42 -1
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/LICENSE +0 -0
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/auth_schemes/__init__.py +0 -0
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/auth_schemes/bearer_auth.py +0 -0
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/auth_server/__init__.py +0 -0
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/auth_server/mfa_client.py +0 -0
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/auth_server/my_account_client.py +0 -0
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/encryption/__init__.py +0 -0
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/encryption/encrypt.py +0 -0
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/store/__init__.py +0 -0
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/store/abstract.py +0 -0
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/telemetry.py +0 -0
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/tests/test_mfa_client.py +0 -0
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/tests/test_my_account_client.py +0 -0
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/src/auth0_server_python/tests/test_telemetry.py +0 -0
- {auth0_server_python-1.0.0b10 → auth0_server_python-1.0.0b11}/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.0b11
|
|
4
4
|
Summary: Auth0 server-side Python SDK
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -129,6 +129,10 @@ async def callback(request: Request):
|
|
|
129
129
|
return RedirectResponse(url="/")
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
+
#### Organizations
|
|
133
|
+
|
|
134
|
+
The SDK supports [Auth0 Organizations](https://auth0.com/docs/organizations) with first-class `organization` and `invitation` parameters on `ServerClient` and `StartInteractiveLoginOptions`. Token claim validation is enforced automatically at callback. For setup, invitation flows, error handling, and reading org data from the session, see [examples/InteractiveLogin.md](examples/InteractiveLogin.md#8-organizations).
|
|
135
|
+
|
|
132
136
|
### 4. Login with Custom Token Exchange
|
|
133
137
|
|
|
134
138
|
If you're migrating from a legacy authentication system or integrating with a custom identity provider, you can exchange external tokens for Auth0 tokens using the OAuth 2.0 Token Exchange specification (RFC 8693):
|
|
@@ -104,6 +104,10 @@ async def callback(request: Request):
|
|
|
104
104
|
return RedirectResponse(url="/")
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
+
#### Organizations
|
|
108
|
+
|
|
109
|
+
The SDK supports [Auth0 Organizations](https://auth0.com/docs/organizations) with first-class `organization` and `invitation` parameters on `ServerClient` and `StartInteractiveLoginOptions`. Token claim validation is enforced automatically at callback. For setup, invitation flows, error handling, and reading org data from the session, see [examples/InteractiveLogin.md](examples/InteractiveLogin.md#8-organizations).
|
|
110
|
+
|
|
107
111
|
### 4. Login with Custom Token Exchange
|
|
108
112
|
|
|
109
113
|
If you're migrating from a legacy authentication system or integrating with a custom identity provider, you can exchange external tokens for Auth0 tokens using the OAuth 2.0 Token Exchange specification (RFC 8693):
|
|
@@ -54,6 +54,7 @@ from auth0_server_python.error import (
|
|
|
54
54
|
MfaRequiredError,
|
|
55
55
|
MissingRequiredArgumentError,
|
|
56
56
|
MissingTransactionError,
|
|
57
|
+
OrganizationTokenValidationError,
|
|
57
58
|
PollingApiError,
|
|
58
59
|
StartLinkUserError,
|
|
59
60
|
)
|
|
@@ -61,6 +62,7 @@ from auth0_server_python.telemetry import Telemetry
|
|
|
61
62
|
from auth0_server_python.utils import PKCE, URL, State
|
|
62
63
|
from auth0_server_python.utils.helpers import (
|
|
63
64
|
build_domain_resolver_context,
|
|
65
|
+
validate_org_claims,
|
|
64
66
|
validate_resolved_domain_value,
|
|
65
67
|
)
|
|
66
68
|
|
|
@@ -96,6 +98,7 @@ class ServerClient(Generic[TStoreOptions]):
|
|
|
96
98
|
state_identifier: str = "_a0_session",
|
|
97
99
|
authorization_params: Optional[dict[str, Any]] = None,
|
|
98
100
|
pushed_authorization_requests: bool = False,
|
|
101
|
+
organization: Optional[str] = None,
|
|
99
102
|
):
|
|
100
103
|
"""
|
|
101
104
|
Initialize the Auth0 server client.
|
|
@@ -112,6 +115,9 @@ class ServerClient(Generic[TStoreOptions]):
|
|
|
112
115
|
state_identifier: Identifier for state data
|
|
113
116
|
authorization_params: Default parameters for authorization requests
|
|
114
117
|
pushed_authorization_requests: Whether to use Pushed Authorization Requests
|
|
118
|
+
organization: Default organization for all login flows from this client.
|
|
119
|
+
Can be an org ID (e.g. 'org_abc123') or an org name (e.g. 'acme-corp').
|
|
120
|
+
Per-login values passed in StartInteractiveLoginOptions always override this.
|
|
115
121
|
"""
|
|
116
122
|
if not secret:
|
|
117
123
|
raise MissingRequiredArgumentError("secret")
|
|
@@ -146,6 +152,7 @@ class ServerClient(Generic[TStoreOptions]):
|
|
|
146
152
|
self._secret = secret
|
|
147
153
|
self._default_authorization_params = authorization_params or {}
|
|
148
154
|
self._pushed_authorization_requests = pushed_authorization_requests # store the flag
|
|
155
|
+
self._organization = organization
|
|
149
156
|
|
|
150
157
|
# Initialize stores
|
|
151
158
|
self._transaction_store = transaction_store
|
|
@@ -207,6 +214,7 @@ class ServerClient(Generic[TStoreOptions]):
|
|
|
207
214
|
|
|
208
215
|
return value.rstrip('/')
|
|
209
216
|
|
|
217
|
+
|
|
210
218
|
async def _resolve_current_domain(self, store_options=None) -> str:
|
|
211
219
|
"""Resolve domain from resolver function or return static domain."""
|
|
212
220
|
if self._domain_resolver:
|
|
@@ -502,6 +510,16 @@ class ServerClient(Generic[TStoreOptions]):
|
|
|
502
510
|
merged_scope = self._merge_scope_with_defaults(requested_scope, audience)
|
|
503
511
|
auth_params["scope"] = merged_scope
|
|
504
512
|
|
|
513
|
+
# Typed org/invitation fields win over anything already in auth_params from authorization_params.
|
|
514
|
+
resolved_org = options.organization or self._organization
|
|
515
|
+
if resolved_org and not resolved_org.strip():
|
|
516
|
+
raise InvalidArgumentError("organization", "organization must not be blank")
|
|
517
|
+
if resolved_org:
|
|
518
|
+
auth_params["organization"] = resolved_org
|
|
519
|
+
|
|
520
|
+
if options.invitation:
|
|
521
|
+
auth_params["invitation"] = options.invitation
|
|
522
|
+
|
|
505
523
|
# Build the transaction data to store with domain
|
|
506
524
|
transaction_data = TransactionData(
|
|
507
525
|
code_verifier=code_verifier,
|
|
@@ -509,6 +527,7 @@ class ServerClient(Generic[TStoreOptions]):
|
|
|
509
527
|
audience=audience,
|
|
510
528
|
domain=origin_domain,
|
|
511
529
|
redirect_uri=auth_params.get("redirect_uri"),
|
|
530
|
+
organization=resolved_org,
|
|
512
531
|
)
|
|
513
532
|
|
|
514
533
|
# Store the transaction data
|
|
@@ -638,8 +657,26 @@ class ServerClient(Generic[TStoreOptions]):
|
|
|
638
657
|
user_info = token_response.get("userinfo")
|
|
639
658
|
user_claims = None
|
|
640
659
|
id_token = token_response.get("id_token")
|
|
660
|
+
expected_org = transaction_data.organization
|
|
661
|
+
|
|
662
|
+
if not user_info and not id_token and expected_org:
|
|
663
|
+
raise OrganizationTokenValidationError(
|
|
664
|
+
"Organization was requested but the token response included neither an ID token nor userinfo; "
|
|
665
|
+
"cannot verify organization membership"
|
|
666
|
+
)
|
|
641
667
|
|
|
642
668
|
if user_info:
|
|
669
|
+
if not isinstance(user_info, dict):
|
|
670
|
+
if expected_org:
|
|
671
|
+
raise OrganizationTokenValidationError(
|
|
672
|
+
"Userinfo response is not a valid claims dictionary; cannot verify organization membership"
|
|
673
|
+
)
|
|
674
|
+
raise ApiError(
|
|
675
|
+
"invalid_response",
|
|
676
|
+
"Userinfo response is not a valid claims dictionary"
|
|
677
|
+
)
|
|
678
|
+
if expected_org:
|
|
679
|
+
validate_org_claims(user_info, expected_org)
|
|
643
680
|
user_claims = UserClaims.parse_obj(user_info)
|
|
644
681
|
elif id_token:
|
|
645
682
|
# Fetch JWKS for signature verification
|
|
@@ -656,6 +693,10 @@ class ServerClient(Generic[TStoreOptions]):
|
|
|
656
693
|
if self._normalize_url(token_issuer) != self._normalize_url(origin_issuer):
|
|
657
694
|
raise IssuerValidationError("ID token issuer mismatch. Ensure your Auth0 domain is configured correctly.")
|
|
658
695
|
|
|
696
|
+
# Organization claim validation — mandatory when org was requested.
|
|
697
|
+
if expected_org:
|
|
698
|
+
validate_org_claims(claims, expected_org)
|
|
699
|
+
|
|
659
700
|
user_claims = UserClaims.parse_obj(claims)
|
|
660
701
|
except ValueError as e:
|
|
661
702
|
raise ApiError("jwks_key_not_found", str(e))
|
|
@@ -1283,7 +1324,10 @@ class ServerClient(Generic[TStoreOptions]):
|
|
|
1283
1324
|
while time.time() < end_time:
|
|
1284
1325
|
# Make token request
|
|
1285
1326
|
try:
|
|
1286
|
-
token_response = await self.backchannel_authentication_grant(
|
|
1327
|
+
token_response = await self.backchannel_authentication_grant(
|
|
1328
|
+
auth_req_id,
|
|
1329
|
+
store_options=store_options,
|
|
1330
|
+
)
|
|
1287
1331
|
return token_response
|
|
1288
1332
|
|
|
1289
1333
|
except Exception as e:
|
|
@@ -2243,10 +2287,45 @@ class ServerClient(Generic[TStoreOptions]):
|
|
|
2243
2287
|
https://datatracker.ietf.org/doc/html/rfc8693
|
|
2244
2288
|
"""
|
|
2245
2289
|
try:
|
|
2246
|
-
# Validate options (Pydantic handles this automatically)
|
|
2247
2290
|
if not isinstance(options, CustomTokenExchangeOptions):
|
|
2248
2291
|
options = CustomTokenExchangeOptions(**options)
|
|
2249
2292
|
|
|
2293
|
+
if not options.subject_token.strip():
|
|
2294
|
+
raise CustomTokenExchangeError(
|
|
2295
|
+
CustomTokenExchangeErrorCode.INVALID_TOKEN_FORMAT,
|
|
2296
|
+
"subject_token cannot be empty or whitespace-only"
|
|
2297
|
+
)
|
|
2298
|
+
if not options.subject_token_type.strip():
|
|
2299
|
+
raise CustomTokenExchangeError(
|
|
2300
|
+
CustomTokenExchangeErrorCode.INVALID_TOKEN_FORMAT,
|
|
2301
|
+
"subject_token_type cannot be empty or whitespace-only"
|
|
2302
|
+
)
|
|
2303
|
+
if options.subject_token.strip().startswith("Bearer "):
|
|
2304
|
+
raise CustomTokenExchangeError(
|
|
2305
|
+
CustomTokenExchangeErrorCode.INVALID_TOKEN_FORMAT,
|
|
2306
|
+
"subject_token should not include 'Bearer ' prefix"
|
|
2307
|
+
)
|
|
2308
|
+
if options.actor_token is not None and not options.actor_token.strip():
|
|
2309
|
+
raise CustomTokenExchangeError(
|
|
2310
|
+
CustomTokenExchangeErrorCode.INVALID_TOKEN_FORMAT,
|
|
2311
|
+
"actor_token cannot be empty or whitespace-only"
|
|
2312
|
+
)
|
|
2313
|
+
if options.actor_token and options.actor_token.strip().startswith("Bearer "):
|
|
2314
|
+
raise CustomTokenExchangeError(
|
|
2315
|
+
CustomTokenExchangeErrorCode.INVALID_TOKEN_FORMAT,
|
|
2316
|
+
"actor_token should not include 'Bearer ' prefix"
|
|
2317
|
+
)
|
|
2318
|
+
if options.actor_token and not options.actor_token_type:
|
|
2319
|
+
raise CustomTokenExchangeError(
|
|
2320
|
+
CustomTokenExchangeErrorCode.MISSING_ACTOR_TOKEN_TYPE,
|
|
2321
|
+
"actor_token_type is required when actor_token is provided"
|
|
2322
|
+
)
|
|
2323
|
+
if options.actor_token_type and not options.actor_token:
|
|
2324
|
+
raise CustomTokenExchangeError(
|
|
2325
|
+
CustomTokenExchangeErrorCode.MISSING_ACTOR_TOKEN,
|
|
2326
|
+
"actor_token is required when actor_token_type is provided"
|
|
2327
|
+
)
|
|
2328
|
+
|
|
2250
2329
|
# Resolve domain
|
|
2251
2330
|
domain = await self._resolve_current_domain(store_options)
|
|
2252
2331
|
metadata = await self._get_oidc_metadata_cached(domain)
|
|
@@ -2309,8 +2388,25 @@ class ServerClient(Generic[TStoreOptions]):
|
|
|
2309
2388
|
"Failed to parse token response as JSON"
|
|
2310
2389
|
)
|
|
2311
2390
|
|
|
2312
|
-
|
|
2313
|
-
|
|
2391
|
+
token_response = TokenExchangeResponse(**token_data)
|
|
2392
|
+
|
|
2393
|
+
# Surface the actor claim for delegation exchanges. Best-effort:
|
|
2394
|
+
# a decode/verify hiccup must not fail an exchange the token
|
|
2395
|
+
# endpoint already accepted, so act stays None on any failure.
|
|
2396
|
+
if options.actor_token and token_response.id_token:
|
|
2397
|
+
try:
|
|
2398
|
+
jwks = await self._get_jwks_cached(domain, metadata)
|
|
2399
|
+
claims = await self._verify_and_decode_jwt(
|
|
2400
|
+
token_response.id_token, jwks, audience=self._client_id
|
|
2401
|
+
)
|
|
2402
|
+
# Apply the same normalized issuer check the login path uses
|
|
2403
|
+
# before trusting any claim from the token.
|
|
2404
|
+
if self._normalize_url(claims.get("iss", "")) == self._normalize_url(metadata.get("issuer")):
|
|
2405
|
+
token_response.act = claims.get("act")
|
|
2406
|
+
except Exception:
|
|
2407
|
+
token_response.act = None
|
|
2408
|
+
|
|
2409
|
+
return token_response
|
|
2314
2410
|
|
|
2315
2411
|
except ValidationError as e:
|
|
2316
2412
|
raise CustomTokenExchangeError(
|
|
@@ -2386,6 +2482,7 @@ class ServerClient(Generic[TStoreOptions]):
|
|
|
2386
2482
|
# Extract user claims from ID token if present
|
|
2387
2483
|
user_claims = None
|
|
2388
2484
|
sid = PKCE.generate_random_string(32) # Default sid
|
|
2485
|
+
|
|
2389
2486
|
if token_response.id_token:
|
|
2390
2487
|
# Fetch JWKS and verify ID token signature
|
|
2391
2488
|
jwks = await self._get_jwks_cached(domain, metadata)
|
|
@@ -2402,6 +2499,8 @@ class ServerClient(Generic[TStoreOptions]):
|
|
|
2402
2499
|
"ID token issuer mismatch. Ensure your Auth0 domain is configured correctly."
|
|
2403
2500
|
)
|
|
2404
2501
|
|
|
2502
|
+
# UserClaims allows extra fields, so any act claim in the
|
|
2503
|
+
# verified id_token is carried onto the session user here.
|
|
2405
2504
|
user_claims = UserClaims.parse_obj(claims)
|
|
2406
2505
|
# Extract sid from token if available
|
|
2407
2506
|
sid = claims.get("sid", sid)
|
|
@@ -2467,7 +2566,7 @@ class ServerClient(Generic[TStoreOptions]):
|
|
|
2467
2566
|
return result
|
|
2468
2567
|
|
|
2469
2568
|
except Exception as e:
|
|
2470
|
-
if isinstance(e, (CustomTokenExchangeError, ApiError)):
|
|
2569
|
+
if isinstance(e, (CustomTokenExchangeError, ApiError, IssuerValidationError)):
|
|
2471
2570
|
raise
|
|
2472
2571
|
raise CustomTokenExchangeError(
|
|
2473
2572
|
CustomTokenExchangeErrorCode.TOKEN_EXCHANGE_FAILED,
|
|
@@ -5,7 +5,7 @@ These Pydantic models provide type safety and validation for all SDK data struct
|
|
|
5
5
|
|
|
6
6
|
from typing import Any, Literal, Optional, Union
|
|
7
7
|
|
|
8
|
-
from pydantic import BaseModel, Field
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class UserClaims(BaseModel):
|
|
@@ -22,6 +22,7 @@ class UserClaims(BaseModel):
|
|
|
22
22
|
email: Optional[str] = None
|
|
23
23
|
email_verified: Optional[bool] = None
|
|
24
24
|
org_id: Optional[str] = None
|
|
25
|
+
org_name: Optional[str] = None
|
|
25
26
|
|
|
26
27
|
class Config:
|
|
27
28
|
extra = "allow" # Allow additional fields not defined in the model
|
|
@@ -91,6 +92,7 @@ class TransactionData(BaseModel):
|
|
|
91
92
|
auth_session: Optional[str] = None
|
|
92
93
|
redirect_uri: Optional[str] = None
|
|
93
94
|
domain: Optional[str] = None
|
|
95
|
+
organization: Optional[str] = None
|
|
94
96
|
|
|
95
97
|
class Config:
|
|
96
98
|
extra = "allow" # Allow additional fields not defined in the model
|
|
@@ -128,6 +130,7 @@ class ServerClientOptionsBase(BaseModel):
|
|
|
128
130
|
transaction_identifier: Optional[str] = "_a0_tx"
|
|
129
131
|
state_identifier: Optional[str] = "_a0_session"
|
|
130
132
|
custom_fetch: Optional[Any] = None # Function type hint would be more complex
|
|
133
|
+
organization: Optional[str] = None
|
|
131
134
|
|
|
132
135
|
|
|
133
136
|
class ServerClientOptionsWithSecret(ServerClientOptionsBase):
|
|
@@ -147,6 +150,8 @@ class StartInteractiveLoginOptions(BaseModel):
|
|
|
147
150
|
pushed_authorization_requests: Optional[bool] = False
|
|
148
151
|
app_state: Optional[Any] = None
|
|
149
152
|
authorization_params: Optional[dict[str, Any]] = None
|
|
153
|
+
organization: Optional[str] = None
|
|
154
|
+
invitation: Optional[str] = None
|
|
150
155
|
|
|
151
156
|
|
|
152
157
|
class LogoutOptions(BaseModel):
|
|
@@ -257,8 +262,8 @@ class CustomTokenExchangeOptions(BaseModel):
|
|
|
257
262
|
organization: Organization identifier for the token exchange (optional)
|
|
258
263
|
authorization_params: Additional OAuth parameters (optional)
|
|
259
264
|
"""
|
|
260
|
-
subject_token: str
|
|
261
|
-
subject_token_type: str
|
|
265
|
+
subject_token: str
|
|
266
|
+
subject_token_type: str
|
|
262
267
|
audience: Optional[str] = None
|
|
263
268
|
scope: Optional[str] = None
|
|
264
269
|
actor_token: Optional[str] = None
|
|
@@ -266,24 +271,6 @@ class CustomTokenExchangeOptions(BaseModel):
|
|
|
266
271
|
organization: Optional[str] = None
|
|
267
272
|
authorization_params: Optional[dict[str, Any]] = None
|
|
268
273
|
|
|
269
|
-
@field_validator('subject_token', 'actor_token')
|
|
270
|
-
@classmethod
|
|
271
|
-
def validate_token_format(cls, v: Optional[str]) -> Optional[str]:
|
|
272
|
-
"""Validate token doesn't have Bearer prefix and isn't whitespace-only."""
|
|
273
|
-
if v is not None:
|
|
274
|
-
if not v.strip():
|
|
275
|
-
raise ValueError("Token cannot be empty or whitespace-only")
|
|
276
|
-
if v.strip().startswith("Bearer "):
|
|
277
|
-
raise ValueError("Token should not include 'Bearer ' prefix")
|
|
278
|
-
return v
|
|
279
|
-
|
|
280
|
-
@model_validator(mode='after')
|
|
281
|
-
def validate_actor_token_type(self) -> 'CustomTokenExchangeOptions':
|
|
282
|
-
"""Ensure actor_token_type is provided if actor_token is present."""
|
|
283
|
-
if self.actor_token and not self.actor_token_type:
|
|
284
|
-
raise ValueError("actor_token_type is required when actor_token is provided")
|
|
285
|
-
return self
|
|
286
|
-
|
|
287
274
|
|
|
288
275
|
class TokenExchangeResponse(BaseModel):
|
|
289
276
|
"""
|
|
@@ -297,6 +284,7 @@ class TokenExchangeResponse(BaseModel):
|
|
|
297
284
|
issued_token_type: Format of issued token
|
|
298
285
|
id_token: OpenID Connect ID token (optional)
|
|
299
286
|
refresh_token: Refresh token (optional)
|
|
287
|
+
act: Actor claim for delegation/impersonation exchanges (optional)
|
|
300
288
|
"""
|
|
301
289
|
access_token: str
|
|
302
290
|
token_type: str = "Bearer"
|
|
@@ -305,6 +293,7 @@ class TokenExchangeResponse(BaseModel):
|
|
|
305
293
|
issued_token_type: Optional[str] = None
|
|
306
294
|
id_token: Optional[str] = None
|
|
307
295
|
refresh_token: Optional[str] = None
|
|
296
|
+
act: Optional[dict[str, Any]] = None
|
|
308
297
|
|
|
309
298
|
|
|
310
299
|
class LoginWithCustomTokenExchangeOptions(BaseModel):
|
|
@@ -313,8 +302,8 @@ class LoginWithCustomTokenExchangeOptions(BaseModel):
|
|
|
313
302
|
|
|
314
303
|
Combines token exchange parameters with session management.
|
|
315
304
|
"""
|
|
316
|
-
subject_token: str
|
|
317
|
-
subject_token_type: str
|
|
305
|
+
subject_token: str
|
|
306
|
+
subject_token_type: str
|
|
318
307
|
audience: Optional[str] = None
|
|
319
308
|
scope: Optional[str] = None
|
|
320
309
|
actor_token: Optional[str] = None
|
|
@@ -322,24 +311,6 @@ class LoginWithCustomTokenExchangeOptions(BaseModel):
|
|
|
322
311
|
organization: Optional[str] = None
|
|
323
312
|
authorization_params: Optional[dict[str, Any]] = None
|
|
324
313
|
|
|
325
|
-
@field_validator('subject_token', 'actor_token')
|
|
326
|
-
@classmethod
|
|
327
|
-
def validate_token_format(cls, v: Optional[str]) -> Optional[str]:
|
|
328
|
-
"""Validate token doesn't have Bearer prefix and isn't whitespace-only."""
|
|
329
|
-
if v is not None:
|
|
330
|
-
if not v.strip():
|
|
331
|
-
raise ValueError("Token cannot be empty or whitespace-only")
|
|
332
|
-
if v.strip().startswith("Bearer "):
|
|
333
|
-
raise ValueError("Token should not include 'Bearer ' prefix")
|
|
334
|
-
return v
|
|
335
|
-
|
|
336
|
-
@model_validator(mode='after')
|
|
337
|
-
def validate_actor_token_type(self) -> 'LoginWithCustomTokenExchangeOptions':
|
|
338
|
-
"""Ensure actor_token_type is provided if actor_token is present."""
|
|
339
|
-
if self.actor_token and not self.actor_token_type:
|
|
340
|
-
raise ValueError("actor_token_type is required when actor_token is provided")
|
|
341
|
-
return self
|
|
342
|
-
|
|
343
314
|
|
|
344
315
|
class LoginWithCustomTokenExchangeResult(BaseModel):
|
|
345
316
|
"""
|
|
@@ -200,6 +200,18 @@ class AccessTokenErrorCode:
|
|
|
200
200
|
DOMAIN_MISMATCH = "domain_mismatch"
|
|
201
201
|
|
|
202
202
|
|
|
203
|
+
class OrganizationTokenValidationError(Auth0Error):
|
|
204
|
+
"""
|
|
205
|
+
Raised when org_id or org_name claim in the ID token fails validation
|
|
206
|
+
against the organization value that was requested at login.
|
|
207
|
+
"""
|
|
208
|
+
code = "organization_token_validation_error"
|
|
209
|
+
|
|
210
|
+
def __init__(self, message: str):
|
|
211
|
+
super().__init__(message)
|
|
212
|
+
self.name = "OrganizationTokenValidationError"
|
|
213
|
+
|
|
214
|
+
|
|
203
215
|
class AccessTokenForConnectionErrorCode:
|
|
204
216
|
"""Error codes for connection-specific token operations."""
|
|
205
217
|
MISSING_REFRESH_TOKEN = "missing_refresh_token"
|
|
@@ -225,6 +237,7 @@ class CustomTokenExchangeErrorCode:
|
|
|
225
237
|
"""Error codes for custom token exchange operations."""
|
|
226
238
|
INVALID_TOKEN_FORMAT = "invalid_token_format"
|
|
227
239
|
MISSING_ACTOR_TOKEN_TYPE = "missing_actor_token_type"
|
|
240
|
+
MISSING_ACTOR_TOKEN = "missing_actor_token"
|
|
228
241
|
TOKEN_EXCHANGE_FAILED = "token_exchange_failed"
|
|
229
242
|
INVALID_RESPONSE = "invalid_response"
|
|
230
243
|
|