Skip to main content

tfrs-auth (Python)

PyPI License: MIT

TFRS AccessToken 客户端工具包:把「PAT / client_credentials → 短 RS256 JWT 换发 (RFC 8693)+ 缓存/刷新」与「Claims/scope 契约 + 纯验签」抽成可复用包,避免每个 MCP 工具 / SDK 重复实现 token 获取与生命周期管理。

下游集成只涉及两个角色:主叫方(换发并携带 token)与被叫方(验签收到的 token)。

SoT = TFRSManager Contract Registry。本包 contract.py 为镜像,变更先改 Manager。 强制执行层(验签中间件/Socket.IO/ESO/默认拒绝 CI)不在本包,留 TFRobotServer。

安装

已发布至 PyPI:https://pypi.org/project/tfrs-auth/(MIT 许可证)。

# 从 PyPI(推荐)
uv add tfrs-auth                  # 核心:契约 + 纯验签(仅依赖 pyjwt[crypto])
uv add "tfrs-auth[httpx]"         # 含客户端 token source / JWKS live 拉取
# 或:pip install "tfrs-auth[httpx]"

# 或从 git + tag(未发布版本)
uv add "tfrs-auth[httpx] @ git+https://cnb.cool/turingfocus/foundation/tfrs-foundation-py@<tag>#subdirectory=packages/tfrs-auth"
  • 核心(契约 + verify.from_jwks 纯验签):只需 pyjwt[crypto],无 httpx 也可 import。
  • 可选 extra httpx:换发 token(token source)或 JwtVerifier.from_url live 拉取 JWKS 时才需要。

用法

主叫方 —— 换发并携带 token

1. 选凭证(三种主体,按调用方身份选)

from tfrs_auth import (
    CachingTokenSource, AsyncCachingTokenSource,
    ClientCredentials, PatCredential, UserJwtCredential,
)

TOKEN_URL = "https://<user-host>/api/v1/oauth/token"
AUDIENCE = "robot:<callee_public_id>"   # 被调机器人 public_id(受众隔离)

# ① client_credentials —— 机器身份(机器人以自身机器凭证换发,A2A 主叫端)
cred = ClientCredentials.for_robot(
    client_id="turingfocus:000101",           # 机器人自身 public_id
    client_secret=machine_client_secret,      # tfp_ 明文(machineClientSecret)
    callee_org_slug="turingfocus",            # → audience=robot:turingfocus:000042
    callee_employee_no="000042",
    scope=["a2a:invoke"],                     # 省略则不带 scope
)
#   或直构:ClientCredentials(client_id=..., client_secret=..., audience=AUDIENCE)

# ② PAT —— 用户 PAT 主体(用户自管理 PAT,tfp_ 不透明令牌)
cred = PatCredential(pat="tfp_...", audience=AUDIENCE, scope="config:read")

# ③ User JWT —— 用户登录态 JWT 主体(session JWT,来自登录)
cred = UserJwtCredential(user_jwt=user_jwt_str, audience=AUDIENCE)

三种凭证喂给同一个 token source(凭证无关)。source 长期持有、复用(它本身就是缓存); .token() 幂等:未临期直返、临期自动换发、并发只放一个换发在途(single-flight)+ 429/503 退避。 同步用 CachingTokenSource、异步用 AsyncCachingTokenSource(API 对称)。

2. 发请求 —— 自动携带合法 Token

方式 A:httpx + BearerAuth(推荐) —— 自动注入 Authorization: Bearer,遇 401 自动刷新重试一次。

import httpx
from tfrs_auth.transport import BearerAuth   # 需 tfrs-auth[httpx]

with CachingTokenSource(cred, token_url=TOKEN_URL) as source:
    with httpx.Client(auth=BearerAuth(source)) as client:
        resp = client.get("https://<api-host>/data")   # 自动带 Bearer <jwt>

# 异步(用法对称):
# async with AsyncCachingTokenSource(cred, token_url=TOKEN_URL) as source:
#     async with httpx.AsyncClient(auth=AsyncBearerAuth(source)) as client:
#         resp = await client.get("https://<api-host>/data")

方式 B:自带 httpx / requests / 任意库(手动取 token 注入 header)

tf-core 尚未开发;若你用自己的 HTTP 库,手动从 source 取 token 字符串塞进请求头:

import requests

with CachingTokenSource(cred, token_url=TOKEN_URL) as source:
    token = source.token()   # Token(.access_token, .token_type="Bearer", .expires_at, ...)
    resp = requests.get(
        "https://<api-host>/data",
        headers={"Authorization": f"{token.token_type} {token.access_token}"},
    )
    # 遇 401 时手动刷新重试一次(等价 BearerAuth 的内部行为):
    # source.invalidate()           # 丢缓存
    # token = source.token()        # 强制重取新 token 后重试

手动方式不自动处理 401:source 仍管缓存 / 临期刷新,但 401 后的「丢缓存重取」需你显式调 source.invalidate().token()。要复用既有 httpx client 传 http_client=...;source 只关闭 自己创建的 client。BearerAuth / AsyncBearerAuth 是 opt-in 模块(from tfrs_auth.transport import, 需 tfrs-auth[httpx];不进包根 __all__,故 import tfrs_auth 仍可在无 httpx 时使用)。

被叫方 —— 验签收到的 token(资源服务器侧)

from tfrs_auth import JwtVerifier

# 线上:从 JWKS URL 构造(拉取 + 按 kid 缓存带 TTL + 未知-kid 限速刷新)
verifier = JwtVerifier.from_url(
    "https://<user-host>/.well-known/jwks.json",
    issuer="https://<user-host>",         # 强烈建议传
    audience=f"robot:{my_robot_public_id}",   # 强烈建议传:受众隔离
)
claims = verifier.verify(access_token)    # 成功返回 Claims;失败抛 TokenVerificationError
assert claims.has_scope("a2a:invoke")

离线/测试零网络:JwtVerifier.from_jwks(jwks_dict, issuer=..., audience=...)verifier 同样长期复用(内含 JWKS 缓存);单次想覆盖受众用 verifier.verify(tok, audience="robot:turingfocus:000123")

⚠️ 资源服务器务必传 issueraudience:二者仅在提供时才校验。不传 audience 会放行签给任意机器人的 token,丧失 aud=robot:{public_id} 受众隔离(issuer 之于颁发者 隔离同理)。from_url 的未知-kid 刷新已内置限速(min_refresh_interval,默认 10s), 避免被构造的随机-kid token 放大成对 JWKS 端点的 DoS。

错误处理(带类型,绝不字符串匹配)

from tfrs_auth import (
    TokenExchangeError,      # 换发失败基类;.retryable 标识 429/503
    TokenVerificationError,  # 验签失败(过期 / 签名错 / aud·iss 不符)
    TransportError,          # JWKS / 换发的网络层失败
    TemporarilyUnavailableError, InvalidClientError, InvalidScopeError,  # 细分子类
)

# 主叫方:退避已内置,兜住自动重试后仍失败的情况
try:
    token = await source.token()
except TokenExchangeError as e:
    ...  # e.retryable 区分 429/503 等瞬时失败

# 被叫方:网络失败与验签失败应同时兜住
try:
    claims = verifier.verify(access_token)
except (TokenVerificationError, TransportError):
    ...  # → 401

速查表

你要做的事 用什么
选凭证 ClientCredentials.for_robot(...) · PatCredential(pat=, audience=) · UserJwtCredential(user_jwt=, audience=)
换发 token(sync / async) CachingTokenSource(cred, token_url=) / AsyncCachingTokenSource(...)
取 token source.token()Token(.access_token, .token_type, .expires_at, .is_expired())
httpx 自动带 Token + 401 刷新 httpx.Client(auth=BearerAuth(source)) / AsyncBearerAuthfrom tfrs_auth.transport
自带库(requests 等)手动带 Token source.token().access_tokenAuthorization: Bearer ...
401 后强制刷新 source.invalidate() → 再 .token()
被叫端验签(线上) JwtVerifier.from_url(jwks_url, issuer=, audience=)
被叫端验签(离线/测试) JwtVerifier.from_jwks(jwks, issuer=, audience=)
拿契约常量/工具 from tfrs_auth import ACTIVE_SCOPES, public_id, robot_audience, scopes_to_str, Scope

模块

模块 内容
contract Scope(8 active + 2 reserved) · Claims · GrantType/SubjectTokenType URN · OAuthErrorCode · JWKS/kid 形状
errors 异常树(InvalidClientError(401) / InvalidTargetError / TemporarilyUnavailableError(503) …)+ from_oauth_error
client Token · TokenSource/AsyncTokenSource · CachingTokenSource / AsyncCachingTokenSource(single-flight + 退避)
credentials ClientCredentials · PatCredential(✅)· UserJwtCredential(✅)
verify JwtVerifier(RS256 + JWKS)
transport BearerAuth / AsyncBearerAuth(httpx auth,✅)

边界与状态

  • 已实现contract / errors / client(sync+async caching) / credentials.ClientCredentials / credentials.PatCredential / credentials.UserJwtCredential / verify / transport.BearerAuth(+ AsyncBearerAuth)。
  • 无骨架:三层 + transport 全部已实现。transport 为 opt-in(from tfrs_auth.transport import,需 tfrs-auth[httpx];不进包根 __all__,保 httpx-optional)。

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tfrs_auth-0.2.1.tar.gz (36.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tfrs_auth-0.2.1-py3-none-any.whl (32.3 kB view details)

Uploaded Python 3

File details

Details for the file tfrs_auth-0.2.1.tar.gz.

File metadata

  • Download URL: tfrs_auth-0.2.1.tar.gz
  • Upload date:
  • Size: 36.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for tfrs_auth-0.2.1.tar.gz
Algorithm Hash digest
SHA256 f7c3f8f79b5b5140acfe8698837f6892f5447ff33c33e032cdc214aa291a813f
MD5 6345ee72f8ddc396b0ef7eeb3085ecc3
BLAKE2b-256 372cffb767e58cdce4421d6a09a0fa1e02708b5e48920a0e53e7c79f9e8edcec

See more details on using hashes here.

File details

Details for the file tfrs_auth-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: tfrs_auth-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 32.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for tfrs_auth-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 99c5725ce3acc65666ecd2faac932903072555e61e93547d517e11c430ee792e
MD5 f57dd2eedd9d4e92afecff034c387163
BLAKE2b-256 e465884ed44320e9e22c3514b41938be16fc3a919be4874044aa6f2c67bca1a6

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 files

0.2.0

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page