概览
大多数健康数据源以 OAuth 2.0 授权。在 Mirobody 中,这在每个 C++vendor::Vendor 内经三个契约操作处理 —— authorize_url、handle_webhook、revoke —— 另外,对于浏览器驱动的 SMART-on-FHIR EHR,还有一个独立的有状态连接服务。
代码库中存在两种 OAuth 角色,别把它们混淆:
src/oauth/—— Mirobody 作为一个 OAuth 2.0 授权服务器(它向 API 客户端签发 token;OIDC 发现 + PKCE)。- Vendor 客户端 —— Mirobody 作为某个健康源(Fitbit、某个 EHR 等)的 OAuth 客户端。本页讲的是客户端这一侧。
OAuth 2.0 —— authorize_url 步骤
authorize_url 构建你重定向用户去的同意 URL。它是纯字符串构建 —— 无网络调用。Fitbit(device/fitbit.cpp)是参考:保密型服务端客户端在 token 步骤用其 secret 认证,因此这里不使用 PKCE。
state是你的关联 / CSRF 值,会在回调时原样回传。- token 交换(在该 vendor 的
/oauth2/token用code换 access token)在带外完成;所得的 access token 就是你存为MIROBODY_VENDOR_<ID>_API_KEY并在fetch时传入的那个。
撤销
revoke 撤销授权。Fitbit 把 token POST 到 /oauth2/revoke,以保密客户端身份认证(HTTP Basic client_id:client_secret):
revoke 则调用其有文档的账号断连 hook(DELETE /wellness-api/rest/user/registration,Bearer)。Withings 没有公开的 token 撤销端点,所以它的 revoke 是一个诚实的桩。
验证 webhook
当某个源对其 webhook 签名时,在信任请求体之前先验证它。Fitbit 用X-Fitbit-Signature = base64(HMAC-SHA1(raw_body))(以 client_secret + "&" 为密钥)签名;客户端重新计算并以恒定时间比较:
handle_webhook 保留为桩 —— Mirobody 不会捏造一个验证方案。
PKCE 与有状态流程(SMART on FHIR、Garmin)
某些流程需要 PKCE:在 authorize 时铸造的code_verifier 必须被持久化,并在 token 交换时重放。裸 authorize_url 无处存放该 verifier,因此 PKCE 同意住在一个有状态的连接层里,而不在 vendor 的 authorize_url 中。这正是 Garmin 的 authorize_url 是桩的原因。
已发布的示例是 SMART-on-FHIR EHR 连接服务(src/health/ehr_connect.*),其中 Mirobody 是某医院 EHR 的 OAuth 客户端:
Authorize
POST /health/ehr/authorize {fhir_base_url} 运行 SMART 发现({base}/.well-known/smart-configuration),生成 PKCE verifier + state,缓存它们,并返回供浏览器使用的该租户 authorize_url。Callback
GET /health/ehr/callback?code=&state= —— 无 bearer;用户由一次性的 state 恢复。缓存的 verifier 被重放,用于在该租户的 token 端点交换 code,access token 按用户缓存。SMART_FHIR_* 系列键配置(client id / redirect uri / scope)。PKCE 生成本身有单元测试(tests/oauth/pkce_test.cpp)。
安全须知
Token 存储
Token 存储
access token 存于
VendorConfig.api_key,源自 MIROBODY_VENDOR_<ID>_*。名称中含 _KEY / _SECRET / _TOKEN 的配置值会在加载时自动加密(Fernet)。绝不记录 token。State / CSRF
State / CSRF
向
authorize_url 传入一个随机 state,并在回调时验证它。EHR 连接服务使用一个一次性的、同时携带用户绑定的缓存 state。恒定时间比较
恒定时间比较
用恒定时间比较(
ct_equal)比对 webhook 签名,绝不用 ==。不要猜测契约
不要猜测契约
无公开签名方案 → webhook 为桩。无有文档的撤销 → revoke 为桩。诚实胜过捏造。
下一步
OAuth 2.0 示例(Fitbit)
一个完整的 OAuth 2.0 设备客户端
Garmin 示例
为何 PKCE / 推送保留为桩
Provider 集成
构建一个新的
vendor::Vendor使用 Provider
端到端的 EHR 连接流程