-
Notifications
You must be signed in to change notification settings - Fork 4
Add token vault subject_token_type access_token to api sdk #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
85e33ae
1ab2c3a
3246a05
89892b0
1f300a5
8070249
b5ea3cd
67e3437
6e3b239
0e1c4c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,14 @@ | ||
| import time | ||
| from typing import Any, Optional | ||
|
|
||
| import httpx | ||
| from authlib.jose import JsonWebKey, JsonWebToken | ||
|
|
||
| from .config import ApiClientOptions | ||
| from .errors import ( | ||
| ApiError, | ||
| BaseAuthError, | ||
| GetAccessTokenForConnectionError, | ||
| InvalidAuthSchemeError, | ||
| InvalidDpopProofError, | ||
| MissingAuthorizationError, | ||
|
|
@@ -390,6 +393,84 @@ async def verify_dpop_proof( | |
|
|
||
| return claims | ||
|
|
||
| async def get_access_token_for_connection(self, options: dict[str, Any]) -> dict[str, Any]: | ||
| """ | ||
| Retrieves a token for a connection. | ||
|
|
||
| Args: | ||
| options: Options for retrieving an access token for a connection. | ||
| Must include 'connection' and 'access_token' keys. | ||
| May optionally include 'login_hint'. | ||
|
|
||
| Raises: | ||
| GetAccessTokenForConnectionError: If there was an issue requesting the access token. | ||
| ApiError: If the token exchange endpoint returns an error. | ||
|
|
||
| Returns: | ||
| Dictionary containing the token response with access_token, expires_in, and scope. | ||
| """ | ||
| # Constants | ||
| SUBJECT_TYPE_ACCESS_TOKEN = "urn:ietf:params:oauth:token-type:access_token" # noqa S105 | ||
| REQUESTED_TOKEN_TYPE_FEDERATED_CONNECTION_ACCESS_TOKEN = "http://auth0.com/oauth/token-type/federated-connection-access-token" # noqa S105 | ||
| GRANT_TYPE_FEDERATED_CONNECTION_ACCESS_TOKEN = "urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token" # noqa S105 | ||
| connection = options.get("connection") | ||
| access_token = options.get("access_token") | ||
|
|
||
| if not connection: | ||
| raise MissingRequiredArgumentError("connection") | ||
|
|
||
| if not access_token: | ||
| raise MissingRequiredArgumentError("access_token") | ||
|
|
||
| client_id = self.options.client_id | ||
| client_secret = self.options.client_secret | ||
| if not client_id or not client_secret: | ||
| raise GetAccessTokenForConnectionError("You must configure the SDK with a client_id and client_secret to use get_access_token_for_connection.") | ||
|
|
||
| metadata = await self._discover() | ||
|
|
||
| token_endpoint = metadata.get("token_endpoint") | ||
| if not token_endpoint: | ||
| raise GetAccessTokenForConnectionError("Token endpoint missing in OIDC metadata") | ||
|
|
||
| # Prepare parameters | ||
| params = { | ||
| "connection": connection, | ||
| "requested_token_type": REQUESTED_TOKEN_TYPE_FEDERATED_CONNECTION_ACCESS_TOKEN, | ||
| "grant_type": GRANT_TYPE_FEDERATED_CONNECTION_ACCESS_TOKEN, | ||
| "client_id": client_id, | ||
| "subject_token": access_token, | ||
| "subject_token_type": SUBJECT_TYPE_ACCESS_TOKEN, | ||
| } | ||
|
|
||
| # Add login_hint if provided | ||
| if "login_hint" in options and options["login_hint"]: | ||
| params["login_hint"] = options["login_hint"] | ||
|
|
||
| async with httpx.AsyncClient() as client: | ||
| response = await client.post( | ||
| token_endpoint, | ||
| data=params, | ||
| auth=(client_id, client_secret) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. non-blocking/minor: When a tenant admin create a new application, the default Application Authentication Method is "Client Secret (Post)". But this code apears to be using the "Client Secret (Basic)" method. Afaik, both offer the same security profile, but I wonder why we use a method that is not the default one here. I imagine that a tenant admin would stumble on this discrepancy and would have to switch over their Application Authentication Method before being able to use the new feature here? Can we get rid of that unnecessary friction? On the opposite, the sibling package
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ) | ||
|
|
||
| if response.status_code != 200: | ||
| error_data = response.json() if response.headers.get( | ||
| "content-type") == "application/json" else {} | ||
| raise ApiError( | ||
| error_data.get("error", "connection_token_error"), | ||
| error_data.get( | ||
| "error_description", f"Failed to get token for connection: {response.status_code}") | ||
| ) | ||
|
|
||
| token_endpoint_response = response.json() | ||
|
|
||
| return { | ||
| "access_token": token_endpoint_response.get("access_token"), | ||
| "expires_at": int(time.time()) + int(token_endpoint_response.get("expires_in", 3600)), | ||
| "scope": token_endpoint_response.get("scope", "") | ||
| } | ||
|
|
||
| # ===== Private Methods ===== | ||
|
|
||
| async def _discover(self) -> dict[str, Any]: | ||
|
|
||

Uh oh!
There was an error while loading. Please reload this page.