Propagate auth session logging ID through token exchange

This commit is contained in:
Darren Hwang
2026-06-04 11:27:04 -07:00
parent 2fac8460a7
commit 6bfb188357
4 changed files with 62 additions and 17 deletions
@@ -6,7 +6,7 @@ export type LoginAccountParams = { "type": "apiKey", apiKey: string, } | { "type
/**
* Optional auth session logging ID propagated to authapi for analytics correlation.
*/
authSessionLoggingId?: string, } | { "type": "chatgptDeviceCode" } | { "type": "chatgptAuthTokens",
authSessionLoggingId?: string | null, } | { "type": "chatgptDeviceCode" } | { "type": "chatgptAuthTokens",
/**
* Access token (JWT) supplied by the client.
* This token is used for backend API requests and email extraction.
@@ -60,7 +60,7 @@ pub enum LoginAccountParams {
codex_streamlined_login: bool,
/// Optional auth session logging ID propagated to authapi for analytics correlation.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
#[ts(optional = nullable)]
auth_session_logging_id: Option<String>,
},
#[serde(rename = "chatgptDeviceCode")]
+1
View File
@@ -199,6 +199,7 @@ pub async fn complete_device_code_login(
&redirect_uri,
&pkce,
&code_resp.authorization_code,
opts.auth_session_logging_id.as_deref(),
)
.await
.map_err(|err| std::io::Error::other(format!("device code exchange failed: {err}")))?;
+59 -15
View File
@@ -338,8 +338,15 @@ async fn process_request(
}
};
match exchange_code_for_tokens(&opts.issuer, &opts.client_id, redirect_uri, pkce, &code)
.await
match exchange_code_for_tokens(
&opts.issuer,
&opts.client_id,
redirect_uri,
pkce,
&code,
opts.auth_session_logging_id.as_deref(),
)
.await
{
Ok(tokens) => {
if let Err(message) = ensure_workspace_allowed(
@@ -355,9 +362,14 @@ async fn process_request(
);
}
// Obtain API key via token-exchange and persist
let api_key = obtain_api_key(&opts.issuer, &opts.client_id, &tokens.id_token)
.await
.ok();
let api_key = obtain_api_key(
&opts.issuer,
&opts.client_id,
&tokens.id_token,
opts.auth_session_logging_id.as_deref(),
)
.await
.ok();
if let Err(err) = persist_tokens_async(
&opts.codex_home,
api_key.clone(),
@@ -530,6 +542,16 @@ fn build_authorize_url(
format!("{issuer}/oauth/authorize?{qs}")
}
fn with_auth_session_logging_id(mut body: String, auth_session_logging_id: Option<&str>) -> String {
if let Some(auth_session_logging_id) = auth_session_logging_id
.filter(|auth_session_logging_id| !auth_session_logging_id.is_empty())
{
body.push_str("&auth_session_logging_id=");
body.push_str(&urlencoding::encode(auth_session_logging_id));
}
body
}
fn generate_state() -> String {
let mut bytes = [0u8; 32];
rand::rng().fill_bytes(&mut bytes);
@@ -729,6 +751,7 @@ pub(crate) async fn exchange_code_for_tokens(
redirect_uri: &str,
pkce: &PkceCodes,
code: &str,
auth_session_logging_id: Option<&str>,
) -> io::Result<ExchangedTokens> {
#[derive(serde::Deserialize)]
struct TokenResponse {
@@ -745,16 +768,20 @@ pub(crate) async fn exchange_code_for_tokens(
redirect_uri = %redirect_uri,
"starting oauth token exchange"
);
let resp = client
.post(token_endpoint)
.header("Content-Type", "application/x-www-form-urlencoded")
.body(format!(
let body = with_auth_session_logging_id(
format!(
"grant_type=authorization_code&code={}&redirect_uri={}&client_id={}&code_verifier={}",
urlencoding::encode(code),
urlencoding::encode(redirect_uri),
urlencoding::encode(client_id),
urlencoding::encode(&pkce.code_verifier)
))
),
auth_session_logging_id,
);
let resp = client
.post(token_endpoint)
.header("Content-Type", "application/x-www-form-urlencoded")
.body(body)
.send()
.await;
let resp = match resp {
@@ -1134,6 +1161,7 @@ pub(crate) async fn obtain_api_key(
issuer: &str,
client_id: &str,
id_token: &str,
auth_session_logging_id: Option<&str>,
) -> io::Result<String> {
// Token exchange for an API key access token
#[derive(serde::Deserialize)]
@@ -1142,17 +1170,21 @@ pub(crate) async fn obtain_api_key(
}
let client = build_reqwest_client_with_custom_ca(reqwest::Client::builder())?;
let token_endpoint = format!("{}/oauth/token", issuer.trim_end_matches('/'));
let resp = client
.post(token_endpoint)
.header("Content-Type", "application/x-www-form-urlencoded")
.body(format!(
let body = with_auth_session_logging_id(
format!(
"grant_type={}&client_id={}&requested_token={}&subject_token={}&subject_token_type={}",
urlencoding::encode("urn:ietf:params:oauth:grant-type:token-exchange"),
urlencoding::encode(client_id),
urlencoding::encode("openai-api-key"),
urlencoding::encode(id_token),
urlencoding::encode("urn:ietf:params:oauth:token-type:id_token")
))
),
auth_session_logging_id,
);
let resp = client
.post(token_endpoint)
.header("Content-Type", "application/x-www-form-urlencoded")
.body(body)
.send()
.await
.map_err(io::Error::other)?;
@@ -1202,6 +1234,7 @@ mod tests {
use super::redact_sensitive_url_parts;
use super::render_login_error_page;
use super::sanitize_url_for_logging;
use super::with_auth_session_logging_id;
#[serial_test::serial(logout_revoke)]
#[tokio::test]
@@ -1505,6 +1538,17 @@ mod tests {
);
}
#[test]
fn token_form_body_includes_auth_session_logging_id_when_present() {
assert_eq!(
with_auth_session_logging_id(
"grant_type=authorization_code".to_string(),
Some("auth-session-123"),
),
"grant_type=authorization_code&auth_session_logging_id=auth-session-123"
);
}
#[test]
fn render_login_error_page_escapes_dynamic_fields() {
let body = String::from_utf8(render_login_error_page(