ChatGPT 登入與認證
codex 跑模型前要拿到 OpenAI 憑證。這一層把「使用者敲 codex login」到「auth.json 寫到 $CODEX_HOME」之間所有事做完:瀏覽器 OAuth、PKCE、device code、API key、access token 五種入口共用同一份 CodexAuth 列舉和 AuthManager 狀態機。login/ crate 負責純認證協議,cli/src/login.rs 負責把協議產物落到磁碟並打 stderr 回饋。
職責
- 多種憑證形態統一:
CodexAuth列舉覆蓋 API key、ChatGPT OAuth、外部 ChatGPT tokens、agent identity JWT、personal access token (PAT)、Bedrock API key 七種模式。codex-rs/login/src/auth/manager.rs:71-79 - 瀏覽器 OAuth + PKCE:
run_login_server在本地起 callback server,生成 PKCE pair 與 state,拼auth_url給瀏覽器跳轉,等回呼拿到 code 後換 token。codex-rs/login/src/server.rs:151-197 - Device code 流:
run_device_code_login先向/deviceauth/usercode申請 user_code,在終端印出 verification_url 讓使用者去瀏覽器輸入,然後輪詢 token 端點。codex-rs/login/src/device_code_auth.rs:234-238 - CLI 入口包裝:
run_login_with_chatgpt/run_login_with_api_key/run_login_with_access_token/run_login_with_device_code在跑前清舊憑證、裝檔案日誌、按forced_login_method校驗是否允許該路徑,完成後std::process::exit。codex-rs/cli/src/login.rs:167-196 - Auth reload 與撤銷:
AuthManager::reload在外部處理程序刷新 token 後重讀auth.json,logout_with_revoke呼叫 OAuth revoke 端點後清本地憑證。codex-rs/login/src/auth/manager.rs:877-925
設計動機
codex 早期只支援 ChatGPT 瀏覽器登入和 API key 兩條路。ChatGPT 登入走 OAuth + PKCE:本地起 callback server,生成 code_verifier / code_challenge,跳瀏覽器後 OpenAI 回呼 http://localhost:PORT/auth/callback?code=...&state=...,server 用 state 防 CSRF,用 code_verifier 換 token。這套流程必須本機有瀏覽器,遠端 SSH 環境就用不了。
於是加了 device code flow (device_code_auth.rs):使用者在終端看到 https://chatgpt.com/device 和一個 user_code,去任意瀏覽器輸入即可。run_login_with_device_code_fallback_to_browser 進一步做 fallback:先嘗試 device code,如果 server 回傳 404(後端沒開 device auth),就退回瀏覽器 server 流程。
CodexAuth 是個列舉而不是 trait 物件,因為各憑證模式的資料結構差異巨大——API key 就一個字串,ChatGPT OAuth 有 access_token / refresh_token / id_token 三元組,agent identity 是 JWT。列舉讓 auth_mode() 這種匹配一目了然,新增模式也是加變體而不是改 vtable。
AgentIdentityAuthPolicy::JwtOnly vs ChatGptAuth 這兩個策略決定「當使用者已經用 ChatGPT 登入時,是否自動註冊一個 agent identity」。預設 ChatGptAuth 允許這種隱式升級,讓 long-running task 不需要單獨再登入 agent identity。
關鍵檔案
codex-rs/login/src/auth/manager.rs:71-79 — pub enum CodexAuth,七種憑證模式的根。codex-rs/login/src/auth/manager.rs:929-977 — login_with_access_token,按 token 形態分派成 PAT 或 agent identity JWT。codex-rs/login/src/server.rs:68-148 — ServerOptions / LoginServer / ShutdownHandle,瀏覽器 OAuth server 的配置與生命週期。codex-rs/login/src/device_code_auth.rs:20-34 — DeviceCode 結構,持有 verification_url、user_code、device_auth_id、polling interval。codex-rs/cli/src/login.rs:52-111 — init_login_file_logging,裝一個最小 file-backed tracing layer 寫 codex-login.log,故意不重用 TUI 的重 telemetry stack。codex-rs/cli/src/login.rs:119-135 — clear_existing_auth_before_login,登入前先 logout,避免舊 token 殘留。codex-rs/tui/src/local_chatgpt_auth.rs:17-59 — load_local_chatgpt_auth,TUI 測試輔助:從本地 auth.json 提取 ChatGPT access_token / account_id / plan_type。CodexAuth 是憑證的唯一真相源,後續 client.rs、provider、telemetry 全從這裡 match 出模式。新增一種憑證就是加變體 + 各處加 match arm。
// login/src/auth/manager.rs:71-79 — 七种凭据模式
pub enum CodexAuth {
ApiKey(ApiKeyAuth),
Chatgpt(ChatgptAuth),
ChatgptAuthTokens(ChatgptAuthTokens),
Headers(AuthHeaders),
AgentIdentity(AgentIdentityAuth),
PersonalAccessToken(PersonalAccessTokenAuth),
BedrockApiKey(BedrockApiKeyAuth),
}run_login_with_chatgpt 是 CLI 層的薄包裝:載入 config → 裝檔案日誌 → 檢查 forced_login_method 是否停用 ChatGPT 路徑 → 清舊憑證 → 起 callback server。注意它回傳 !(never),所有出口都是 process::exit,不會讓呼叫方繼續往下跑。
// cli/src/login.rs:167-184 — ChatGPT 登录入口,薄包装 + 硬退出
pub async fn run_login_with_chatgpt(cli_config_overrides: CliConfigOverrides) -> ! {
let config = load_config_or_exit(cli_config_overrides).await;
let _login_log_guard = init_login_file_logging(&config);
tracing::info!("starting browser login flow");
if matches!(config.forced_login_method, Some(ForcedLoginMethod::Api)) {
eprintln!("{CHATGPT_LOGIN_DISABLED_MESSAGE}");
std::process::exit(1);
}
let forced_chatgpt_workspace_id = config.forced_chatgpt_workspace_id.clone();
match login_with_chatgpt(
config.codex_home.to_path_buf(),
forced_chatgpt_workspace_id,
config.cli_auth_credentials_store_mode,
config.auth_keyring_backend_kind(),
config.auth_route_config(),
).await { /* Ok/Err 分别 exit */ }
}login_with_access_token 是「按 token 形態自動分派」的典型——同一個 --with-access-token flag 進來,內部根據 JWT 結構判定走 PAT 路徑還是 agent identity JWT 路徑,落盤 AuthDotJson 時 auth_mode 欄位也對應不同,這樣舊的 codex 版本回滾時也能正確反序列化。
// login/src/auth/manager.rs:929-970 — access token 自动分派 PAT vs AgentIdentity
pub async fn login_with_access_token(/* ... */) -> std::io::Result<()> {
let auth_dot_json = match classify_codex_access_token(access_token) {
CodexAccessToken::PersonalAccessToken(access_token) => {
let auth = PersonalAccessTokenAuth::load(access_token, auth_route_config).await?;
ensure_personal_access_token_workspace_allowed(forced_chatgpt_workspace_id, &auth)?;
AuthDotJson { auth_mode: None, personal_access_token: Some(access_token.to_string()), .. }
}
CodexAccessToken::AgentIdentityJwt(jwt) => {
verified_record_from_jwt(jwt, &base_url, auth_route_config).await?;
AuthDotJson { auth_mode: Some(AuthMode::AgentIdentity),
agent_identity: Some(AgentIdentityStorage::Jwt(jwt.to_string())), .. }
}
};
save_auth(codex_home, &auth_dot_json, auth_credentials_store_mode, keyring_backend_kind)
}資料流
邊界與失敗
forced_login_method互斥:管理員可強制只能用 API 或 ChatGPT,CLI 偵測到不符時直接 stderr 報錯 exit 1,不會等到 OAuth 跑一半被拒。codex-rs/cli/src/login.rs:172-175- device code 不支援時 fallback:
run_login_with_device_code_fallback_to_browser先跑 device code,若回傳ErrorKind::NotFound說明後端未開,自動退回瀏覽器 server 流程;其他錯誤仍按失敗處理。codex-rs/cli/src/login.rs:389-419 auth.json落盤權限:Unix 上OpenOptionsExt::mode(0o600)強制只屬主可讀,避免憑證被同組使用者看到。codex-rs/cli/src/login.rs:69-78- PAT workspace 限制:
ensure_personal_access_token_workspace_allowed在 PAT 登入時校驗forced_chatgpt_workspace_id是否匹配,防止 PAT 跨 workspace 誤用。codex-rs/login/src/auth/manager.rs:979-985
小結
login/ crate 把五種登入入口統一到 CodexAuth 列舉和 AuthManager 狀態機:瀏覽器 OAuth + PKCE 適合本地、device code 適合遠端、API key / PAT / agent identity JWT 適合自動化場景。cli/src/login.rs 是薄包裝,負責裝檔案日誌、清舊憑證、按 forced_login_method 守門,所有出口都 process::exit。落盤後 auth.json 是後續所有模型請求的憑證來源,詳見 LLM 客戶端與 Responses API。登入完成後使用者進 TUI 或跑 codex exec,見 CLI 入口與子命令分發。