{"record":{"id":"eb8bf9a2c967cb5a","repo":"can1357/oh-my-pi","slug":"https-loopback-redirect-uris-require-oauth-callbac","errorCode":null,"errorMessage":"HTTPS loopback redirect URIs require oauth.callbackPort to point at the local HTTP callback listener behind your TLS terminator","messagePattern":"HTTPS loopback redirect URIs require oauth\\.callbackPort to point at the local HTTP callback listener behind your TLS terminator","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/mcp/oauth-flow.ts","lineNumber":164,"sourceCode":"}\n\nfunction parseRedirectUri(redirectUri: string | undefined): URL | undefined {\n\treturn redirectUri ? new URL(redirectUri) : undefined;\n}\n\nfunction getUriPort(uri: URL): number {\n\tif (uri.port !== \"\") return Number(uri.port);\n\treturn uri.protocol === \"https:\" ? 443 : 80;\n}\n\nfunction validateRedirectConfig(config: MCPOAuthConfig, redirectUri: string | undefined): void {\n\tconst parsed = parseRedirectUri(redirectUri);\n\tif (parsed?.protocol !== \"https:\" || !isLoopbackHostname(parsed.hostname)) {\n\t\treturn;\n\t}\n\n\tif (config.callbackPort === undefined) {\n\t\tthrow new Error(\n\t\t\t\"HTTPS loopback redirect URIs require oauth.callbackPort to point at the local HTTP callback listener behind your TLS terminator\",\n\t\t);\n\t}\n\n\tif (config.callbackPort === getUriPort(parsed)) {\n\t\tthrow new Error(\n\t\t\t\"HTTPS loopback redirect URIs cannot reuse the same local port; terminate TLS separately and forward to oauth.callbackPort\",\n\t\t);\n\t}\n}\n\nfunction resolveCallbackPort(callbackPort: number | undefined, redirectUri: string | undefined): number {\n\tif (callbackPort !== undefined) return callbackPort;\n\n\tconst parsed = parseRedirectUri(redirectUri);\n\tif (parsed?.protocol !== \"http:\" || !isLoopbackHostname(parsed.hostname)) {\n\t\treturn DEFAULT_PORT;\n\t}","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/mcp/oauth-flow.ts#L146-L182","documentation":"validateRedirectConfig handles the case where the redirect URI is an HTTPS loopback URL. The library's local callback listener speaks plain HTTP, so with an https redirect URI you must terminate TLS separately (reverse proxy/TLS terminator) and run the local listener on a different port, which must be declared via oauth.callbackPort. If callbackPort is missing, the library cannot know where to bind the HTTP listener and throws.","triggerScenarios":"Configuring an https:// loopback redirect URI (e.g. https://localhost:8443/callback) in oauth.redirectUri while leaving oauth.callbackPort undefined, then resolving callback options via resolveCallbackOptions.","commonSituations":"Users behind a local TLS terminator (caddy, nginx) who registered an https loopback redirect with their provider but forgot to tell the library which plain-HTTP port the local listener should use; assuming the library can serve HTTPS itself.","solutions":["Set oauth.callbackPort to the port your local HTTP callback listener should bind, distinct from the HTTPS port in the redirect URI","Configure your TLS terminator to forward from the https redirect port to the callbackPort","Alternatively, switch the redirect URI to plain http://localhost:<port> if your provider allows insecure loopback redirects"],"exampleFix":"// before\n\"oauth\": { \"redirectUri\": \"https://localhost:8443/auth/callback\" }\n// after: local HTTP listener on 1455, TLS terminator forwards 8443 -> 1455\n\"oauth\": { \"redirectUri\": \"https://localhost:8443/auth/callback\", \"callbackPort\": 1455 }","handlingStrategy":"validation","validationCode":"const uri = config.oauth?.redirectUri;\nif (uri) {\n  const p = new URL(uri);\n  const loopback = ['localhost', '127.0.0.1', '[::1]'].includes(p.hostname);\n  if (p.protocol === 'https:' && loopback && config.oauth.callbackPort === undefined) {\n    throw new Error('https loopback redirectUri requires oauth.callbackPort for the local HTTP listener');\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  startOAuthFlow(config);\n} catch (e) {\n  if (e.message.includes('HTTPS loopback redirect URIs require oauth.callbackPort')) {\n    throw new Error('Add oauth.callbackPort (a plain-HTTP port behind your TLS terminator) to your MCP oauth config');\n  } else throw e;\n}","preventionTips":["If you don't run a TLS terminator locally, use plain http://localhost:<port> redirect URIs","When using https loopback redirects, always pair them with a distinct oauth.callbackPort","Document the terminator topology (https port -> callbackPort) in your config comments","Test the OAuth flow after any redirect/terminator config change"],"tags":["oauth","tls","configuration","loopback"],"backgroundTag":"oauth-loopback-redirect-tls","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}