Fix OAuth callback path to /auth/callback and add login example

The redirect_uri path must be /auth/callback (not /callback) to match
what OpenAI's auth server expects for this client ID. Also use localhost
instead of 127.0.0.1 to match the Codex CLI convention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-10 15:39:15 -04:00
parent d26149bf33
commit 8fc03130ef
2 changed files with 31 additions and 7 deletions

View file

@ -0,0 +1,24 @@
use arc_openai_oauth::{extract_account_id, run_browser_flow, DEFAULT_CLIENT_ID, DEFAULT_ISSUER};
#[tokio::main]
async fn main() {
match run_browser_flow(DEFAULT_ISSUER, DEFAULT_CLIENT_ID).await {
Ok(tokens) => {
println!("Login successful!");
if let Some(account_id) = extract_account_id(&tokens) {
println!("Account ID: {account_id}");
}
println!(
"Access token: {}...",
&tokens.access_token[..20.min(tokens.access_token.len())]
);
if let Some(expires_in) = tokens.expires_in {
println!("Expires in: {expires_in}s");
}
}
Err(e) => {
eprintln!("Login failed: {e}");
std::process::exit(1);
}
}
}

View file

@ -306,7 +306,7 @@ pub async fn poll_device_flow(
device: &DeviceAuthResponse,
) -> Result<TokenResponse, String> {
let poll_url = format!("{issuer}/api/accounts/deviceauth/token");
let redirect_uri = format!("http://127.0.0.1:{OAUTH_PORT}/callback");
let redirect_uri = format!("http://localhost:{OAUTH_PORT}/auth/callback");
let mut attempt = 0u32;
loop {
@ -365,7 +365,7 @@ pub async fn start_callback_server(
port: u16,
expected_state: String,
) -> Result<(u16, tokio::sync::oneshot::Receiver<String>), String> {
let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{port}"))
let listener = tokio::net::TcpListener::bind(format!("localhost:{port}"))
.await
.map_err(|e| format!("Failed to bind callback server: {e}"))?;
let actual_port = listener
@ -381,7 +381,7 @@ pub async fn start_callback_server(
let expected_state = std::sync::Arc::new(expected_state);
let app = axum::Router::new().route(
"/callback",
"/auth/callback",
axum::routing::get(
move |axum::extract::Query(params): axum::extract::Query<CallbackParams>| async move {
if params.state != *expected_state {
@ -433,7 +433,7 @@ pub async fn start_callback_server(
pub async fn run_browser_flow(issuer: &str, client_id: &str) -> Result<TokenResponse, String> {
let pkce = generate_pkce();
let state = generate_state();
let redirect_uri = format!("http://127.0.0.1:{OAUTH_PORT}/callback");
let redirect_uri = format!("http://localhost:{OAUTH_PORT}/auth/callback");
let (_port, code_rx) = start_callback_server(OAUTH_PORT, state.clone()).await?;
let auth_url = build_authorize_url(issuer, client_id, &redirect_uri, &pkce, &state);
@ -982,7 +982,7 @@ mod tests {
let client = reqwest::Client::new();
client
.get(format!(
"http://127.0.0.1:{port}/callback?code=abc&state=test-state"
"http://localhost:{port}/auth/callback?code=abc&state=test-state"
))
.send()
.await
@ -1001,7 +1001,7 @@ mod tests {
let client = reqwest::Client::new();
let resp = client
.get(format!(
"http://127.0.0.1:{port}/callback?code=abc&state=wrong-state"
"http://localhost:{port}/auth/callback?code=abc&state=wrong-state"
))
.send()
.await
@ -1019,7 +1019,7 @@ mod tests {
let client = reqwest::Client::new();
let resp = client
.get(format!(
"http://127.0.0.1:{port}/callback?code=abc&state=test-state"
"http://localhost:{port}/auth/callback?code=abc&state=test-state"
))
.send()
.await