Skip to content

Repository files navigation

此项目更新比较快~

Passkey Auth

一个使用 Python、Flask 和 py_webauthn(包名 webauthn)实现的 passkey 注册、登录、OAuth / SSO 认证服务。

特性

  • 用户可自定义用户名
  • 支持注册、用户名登录和无用户名 passkey 登录
  • 浏览器侧接口只返回最小登录状态,不把用户名放进 URL 或普通 UI 响应
  • 提供服务端验证 API,方便作为自建 OAuth / SSO 的身份校验入口
  • 内置 OAuth authorization code flow
  • 内置第三方网页跳转和 OAuth 回跳示例,复用标准 OAuth client 管道
  • 内置 link challenge flow 示例:模拟 login.xxxxx 跳转到 auth.xxxxx/{challenge}
  • 使用标准 WebAuthn 浏览器接口:navigator.credentials.create() / navigator.credentials.get()
  • 后端 passkey 逻辑位于可导入库函数中
  • SQLite 本地存储用户和 credential public key
  • 可选浏览器遥测:全局性能短路、按用户能力下发、独立统计库和 Management 可视化
  • 支持 Chrome、Safari、Firefox、Edge 等现代浏览器

AI 协作声明

本项目由仓库所有者与 OpenAI Codex 协作开发。该声明由仓库所有者主动保留,用于透明记录 AI-assisted development;项目授权、免责声明和责任限制以 Apache License 2.0 为准。贡献范围见 AI_ATTRIBUTION.md

高级文档与 Agent 入口

想了解完整 OAuth / SSO 接入、安全模型、生产部署、扩展方式或 Agent 友好的项目地图,请从 项目 Wiki 开始。

推荐阅读路径:

运行

python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python -m jstu_passkey.app

桌面 App 构建

仓库提供 .github/workflows/build-desktop-apps.yml,可生成包含 Python 运行时、依赖和前端资源的自包含程序:

  • Windows x64:Passkey-Auth-windows-x64.zip
  • Linux x64:Passkey-Auth-linux-x64.tar.gz
  • macOS Intel:Passkey-Auth-macos-x64.zip
  • macOS Apple Silicon:Passkey-Auth-macos-arm64.zip

在 GitHub Actions 中手动运行 Build desktop apps 后,可从该次运行的 Artifacts 下载。推送 V* 标签(例如 V3.0.0)时,产物还会自动附加到对应 GitHub Release。

桌面版本启动后会自动打开浏览器。数据库与固定的 Flask Session Secret 保存在 当前用户的应用数据目录,而不是临时打包目录:

  • Windows:%APPDATA%\Passkey-Auth
  • macOS:~/Library/Application Support/Passkey-Auth
  • Linux:${XDG_DATA_HOME:-~/.local/share}/Passkey-Auth

认证主库、可选遥测库和固定 Secret 都保存在该目录;遥测库仍只会在启用遥测或 管理员读取统计时创建。

构建产物未进行 Apple Developer ID 或 Windows Authenticode 签名,因此首次启动时 可能出现 Gatekeeper 或 SmartScreen 提示。正式公开分发时建议增加平台代码签名。

当前版本使用全新的 v2 数据结构,默认数据库为 instance/passkeys-v2.sqlite3。旧版 passkeys.sqlite3 不会自动迁移;如显式把 PASSKEY_DATABASE 指向旧库,应用会拒绝启动并提示使用新数据库。

用户管理

使用一次性 URL 创建首个或额外的全权限管理员:

PORT=5003 \
PASSKEY_ORIGIN=http://localhost:5003 \
.venv/bin/python -m jstu_passkey.app --reregister-admin qpwoeiruty

然后访问:

http://localhost:5003/qpwoeiruty

通过反向代理部署时使用真实 HTTPS 地址访问;如需让开发服务器直接监听局域网, 另外设置 HOST=0.0.0.0,并确保 PASSKEY_ORIGIN 与浏览器实际 URL 完全一致。

恢复 token 只能使用 A-Za-z0-9_-,长度为 8–128 字符,并且不能与 apidemooauthstaticmanagement_error 等一级路由冲突。格式或路由冲突会在服务监听端口前输出醒目的启动错误并退出。

管理员创建成功后,一次性 URL 立即失效。登录后打开:

http://localhost:5003/management

管理端支持:

  • 用户、Passkey、admin / login / demo 权限及平台白黑名单
  • OAuth Client 平台、回调地址、启停和 secret 轮换
  • 注册永久开启、关闭或自定义期限临时开启
  • 登录历史、管理审计、CSV 导出和日志清理
  • 遥测总开关、逐用户采集策略、设备分布统计、CSV 导出和独立数据清理
  • 撤销用户会话、停用和删除用户

敏感管理写入除管理员 Session、CSRF 和最近一次 Passkey 验证外,还要求当前 X-Action-Token。该 token 的明文仅在登录或二次 Passkey 验证成功后返回给当前 浏览器;服务端只保存与用户及当前 Session 绑定的 hash。每次成功写入都会在 JSON 中返回 next_action_token,旧 token 立即失效;缺失或重放旧 token 会要求重新完成 Passkey 验证。只读 GET 接口不受影响。

Management UI 还会在支持 WebCrypto 和 Server-Sent Events 的浏览器里建立一个轻量 管理通道:当前页面生成临时 P-256 签名密钥,服务端通过 SSE 推送 nonce 和推荐 ACK 间隔,浏览器回传签名 ACK、页面前后台状态和网络提示。通道启动后,同一 Session 的管理写入还必须带 X-Management-Channel-* 签名 proof;服务端用单调 counter 拒绝重放,并按前后台、低速网络或省流量模式自动放宽心跳间隔。该通道只 证明当前页面仍持有临时私钥和最近 nonce,不替代 Passkey、CSRF、Session 或 X-Action-Token

CSV 使用 UTF-8 BOM 和稳定英文列名,不包含 credential 公钥、Client Secret hash、session 或 token。登录历史包含原始 IP 和完整 User-Agent,应按部署地的隐私要求使用。

遥测默认关闭。总开关关闭时,请求热路径只进行一次内存布尔检查,不打开遥测 数据库、不修改 HTML、不加载浏览器采集脚本,也不创建网络任务。默认使用内置 Telemetry;也可切换到 jason-telemetry 或任意 HTTP POST 接收端,并选择浏览器直连 或 Passkey-Auth 服务端异步转发。启用后可以针对 匿名访客和每个已登录用户分别选择屏幕、硬件摘要、显示偏好、网络、字体和电池 能力;浏览器在空闲时使用同源 beacon 上报,并按操作系统动态加载匹配模块。完整 设计、隐私边界和浏览器兼容说明见 遥测文档

完整 OAuth 接入说明见:OAuth 接入开发文档

然后打开:

http://localhost:XXXX

如果本机 XXXX 端口被占用,可以换端口。WebAuthn 的 origin 必须和浏览器地址一致:

PORT=5001 PASSKEY_ORIGIN=http://localhost:5001 .venv/bin/python -m jstu_passkey.app

未设置 PASSKEY_ORIGIN 时,服务会自动使用当前请求地址,例如 http://localhost:5002

Passkey / WebAuthn 要求安全上下文。localhost 属于浏览器允许的安全上下文;如果部署到线上,请使用 HTTPS,并把 PASSKEY_RP_IDPASSKEY_ORIGIN 改成你的真实域名。

如果要用手机或其他局域网设备测试本机服务,可启动内置本地 HTTPS 反向代理:

.venv/bin/python -m jstu_passkey.local_https_proxy

该命令会自动检测本机局域网 IP,生成自签名证书,并用 https://<YOUR_HOSTNAME>.local:5443 作为 PASSKEY_ORIGIN。Flask 后端仍只监听 127.0.0.1:5003,HTTPS 代理会注入可信 X-Forwarded-* 头,并在启动输出里显示检测到的 局域网 IP 方便排查网络。浏览器首次访问会提示证书不受信任,这是自签名证书的预期行为; 证书和私钥保存在系统用户数据目录,不会写入仓库。

如需创建管理员,也可以直接把一次性 token 传给 HTTPS 反代启动器:

.venv/bin/python -m jstu_passkey.local_https_proxy --reregister-admin qpwoeiruty

如果自动检测到的 .local 主机名不适合当前网络,可显式指定 origin。WebAuthn 不接受裸 IP 作为 Passkey RP ID,因此这里必须使用本地域名、mDNS 主机名或 hosts / DNS 记录:

.venv/bin/python -m jstu_passkey.local_https_proxy \
  --origin https://passkey.local:5443

环境变量

默认配置集中在 jstu_passkey/config.py,运行时仍可通过环境变量覆盖:

PASSKEY_RP_ID=localhost
PASSKEY_ORIGIN=http://localhost:5000
PASSKEY_RP_NAME="JSTU Passkey"
PASSKEY_DATABASE=/path/to/passkeys-v2.sqlite3
PASSKEY_TELEMETRY_DATABASE=/path/to/passkeys-telemetry-v1.sqlite3
FLASK_SECRET_KEY=change-me
PASSKEY_REGISTRATION_ENABLED=false
PASSKEY_HOME_AUTH_ENABLED=true
PASSKEY_SERVER_API_TOKEN=change-this-server-token
PASSKEY_OAUTH_CLIENT_ID=jstu-passkey-client
PASSKEY_OAUTH_CLIENT_SECRET=jstu-passkey-secret
PASSKEY_OAUTH_CLIENT_NAME="Passkey OAuth Client"
PASSKEY_OAUTH_REDIRECT_URIS=http://localhost:8765/api/auth/callback
PASSKEY_OAUTH_CHALLENGE_TTL_SECONDS=300
PASSKEY_TRUST_PROXY_HEADERS=false
PASSKEY_HTTP3_ALT_SVC=
PASSKEY_SERVER_TIMING_ENABLED=true

PASSKEY_REGISTRATION_ENABLED 默认关闭,仅作为新数据库尚未保存管理设置时的初始值。 之后可在 /management 中永久开启、关闭或按自定义到期时间临时开启。

PASSKEY_HOME_AUTH_ENABLED 默认开启,主页会加载 main.js,启用 Logo 隐藏注册/登录交互和对应快捷键。设为 false 后,主页仅展示品牌页面。

HTTP/3/QUIC 通常由 Caddy、NGINX、Cloudflare 等 HTTPS 反向代理终止,Flask 开发服务器本身不提供 HTTP/3。线上部署时设置 PASSKEY_ORIGIN=https://auth.xxxxx;如果代理会传递可信 X-Forwarded-* 头,再开启 PASSKEY_TRUST_PROXY_HEADERS=true。确认代理已经支持 HTTP/3 后,可设置 PASSKEY_HTTP3_ALT_SVC='h3=":443"; ma=86400' 让 HTTPS 响应宣告 HTTP/3 替代服务。

PASSKEY_SERVER_TIMING_ENABLED 默认开启,会发送低敏的 Server-Timing: app;dur=...,方便在 Chrome DevTools 的 Network 面板里查看 Flask 应用处理总耗时;不包含 WebAuthn、OAuth、用户或 token 内部细节。

PASSKEY_TELEMETRY_DATABASE 默认使用 instance/passkeys-telemetry-v1.sqlite3。该库与认证主库分离,只有管理员打开 遥测面板读取历史统计,或内置模式收到样本时才会打开。外部模式不会为了本地图表 重复落盘,且只有被选中的适配器会按需加载。jason-telemetry v13 可通过一次性 配对码与 Passkey-Auth 自动协商并保存专用 API Key,无需在浏览器或 Management 响应中显示最终密钥。旧的 PASSKEY_TELEMETRY_TOKEN_URL / PASSKEY_TELEMETRY_API_KEY 仍保留兼容端点,并在 尚未保存 Management 设置时作为旧部署的初始启用信号;新功能不需要把 API key 发送给浏览器。

服务端验证 API

浏览器中的 Passkey 登录和二次验证统一跳转到 /auth/passkey Logo 页面完成, 验证成功后再返回发起页面。旧的通用 /api/login/options/api/login/verify 已删除,普通业务页面不能直接调用 WebAuthn 验证接口。

浏览器登录或二次验证成功后,前端接口返回登录结果和本次 Session 的 action token:

{"ok": true, "mode": "login", "action_token": "..."}

action token 只用于同源浏览器发起敏感状态变更,不是身份信息或服务端 API token。

如需让你的业务后端确认用户身份,由业务后端调用:

POST /api/server/session/verify
Authorization: Bearer $PASSKEY_SERVER_API_TOKEN
Content-Type: application/json

请求体可以省略,此时接口会验证当前请求携带的 Flask session cookie;也可以显式传入 cookie,便于服务端转发验证:

{"sessionCookie": "session=..."}

验证成功时返回服务端可用的身份信息:

{
  "ok": true,
  "authenticated": true,
  "user": {
    "sub": "stable-user-handle",
    "id": 1,
    "username": "alice",
    "createdAt": 1780000000
  }
}

sub 来自 WebAuthn user handle,适合作为 OAuth / SSO 场景里的稳定用户标识。这个接口默认只有设置了 PASSKEY_SERVER_API_TOKEN 且 Bearer token 匹配时才会返回身份信息。

OAuth 与示例页面

启动后打开:

http://localhost:5002/demo/oauth

流程:

  1. OAuth Client 示例页面跳转到 /oauth/authorize
  2. Passkey-Auth 展示极简 Logo 页面,并自动呼出 passkey 验证
  3. 登录成功后回调到 /demo/oauth/callback?code=...&state=...
  4. 示例回调后端使用 client_id/client_secret/code/redirect_uri 换取 token 和用户信息
  5. 页面显示登录成功或失败

OAuth 相关端点:

GET  /oauth/authorize
POST /oauth/authorize/complete
POST /oauth/token
GET  /oauth/userinfo

默认 OAuth client:

client_id=jstu-passkey-client
client_secret=jstu-passkey-secret
redirect_uri=http://localhost:5002/demo/oauth/callback

如果你部署到其他域名或端口,设置 PASSKEY_ORIGIN,并用 PASSKEY_OAUTH_CLIENT_IDPASSKEY_OAUTH_CLIENT_SECRETPASSKEY_OAUTH_REDIRECT_URIS 配置生产 client。PASSKEY_OAUTH_REDIRECT_URIS 支持逗号或换行分隔多个精确 callback 地址。OAuth callback URL 里只携带必要的 code/state,不会携带 username

本地开发时,默认 OAuth client 也允许 Hyping Web UI 的 callback:

http://localhost:8765/api/auth/callback

如果 Hyping 使用了其他端口、域名或 HTTPS 地址,请把那个精确 callback URL 加到 PASSKEY_OAUTH_REDIRECT_URIS

OAuth Client 首次从 PASSKEY_OAUTH_CLIENT_* 导入全新数据库,后续在 /management 中管理。旧的 PASSKEY_OAUTH_DEMO_* 配置不再读取。

第三方网页跳转和回调示例

启动后打开:

http://localhost:5002/demo/third-party

流程:

  1. 模拟第三方网页生成 state,跳转到 /oauth/authorize
  2. Passkey-Auth 展示和根目录一致的极简 Logo 页面,并自动呼出 passkey 验证
  3. 登录成功后跳回 /demo/third-party/callback?code=...&state=...
  4. 第三方 callback 校验 state,用 code/oauth/token
  5. 第三方 callback 再用 access_token/oauth/userinfo
  6. 页面展示 callback 参数、token 响应和 userinfo 响应

这个页面会展示 access_token,只用于本地调试和理解 OAuth 回跳流程。生产环境不要把 access token 直接暴露在浏览器页面上。

链接跳转 Challenge 示例

启动后打开:

http://localhost:5002/demo/link-login

这个示例模拟你描述的域名形态:

https://login.xxxxx/                 原网站登录页
https://auth.xxxxx/oauth/challenge/{challenge}
https://login.xxxxx/callback         原网站回调页

本地流程:

  1. 原网站页面输入用户名并提交到 /demo/link-login/start
  2. 原网站后端创建一次性 challenge,保存 username/state/return_uri/client_id
  3. 浏览器跳转到 /oauth/challenge/{challenge}
  4. Auth WebUI 用该用户名发起 passkey 验证
  5. 验证成功后,Auth 后端把 challenge 标记为完成,并签发 challenge_result
  6. 浏览器跳回 /demo/link-login/callback?challenge=...&challenge_result=...&state=...&status=success
  7. 原网站 callback 校验自己的 state,再服务端验证 challenge_result 签名和一次性 challenge 状态,校验成功后登录

注意:status=success 只是便于页面展示,不能作为登录依据。真正可信的是服务端校验后的 challenge_result,并且同一个 challenge 只能消费一次。

如果部署到两个真实子域名,WebAuthn 配置通常类似:

PASSKEY_RP_ID=xxxxx
PASSKEY_ORIGIN=https://auth.xxxxx
PASSKEY_OAUTH_REDIRECT_URIS=https://login.xxxxx/callback
PASSKEY_TRUST_PROXY_HEADERS=true
PASSKEY_HTTP3_ALT_SVC='h3=":443"; ma=86400'

PASSKEY_RP_ID=xxxxx 允许 auth.xxxxx 作为 passkey 的 RP 子域使用;浏览器实际打开 Auth WebUI 的 origin 必须和 PASSKEY_ORIGIN 一致。

可导入函数

核心函数在 jstu_passkey.webauthn_service

  • build_registration_options(...)
  • verify_registration(...)
  • build_authentication_options(...)
  • verify_authentication(...)

Flask 服务只是其中一种使用方式。

About

Modern passkey OAuth and SSO with WebAuthn, contemporary UI design, and user-first passwordless login experience.

Topics

Resources

Security policy

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages