{"record":{"id":"e564abb8db4e5892","repo":"git-ecosystem/git-credential-manager","slug":"only-localhost-is-supported-as-a-redirect-uri","errorCode":null,"errorMessage":"Only localhost is supported as a redirect URI.","messagePattern":"Only localhost is supported as a redirect URI\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Core/Authentication/OAuth/OAuth2SystemWebBrowser.cs","lineNumber":85,"sourceCode":"</script></body></html>\";\n\n        private readonly ISessionManager _sessionManager;\n        private readonly OAuth2WebBrowserOptions _options;\n\n        public OAuth2SystemWebBrowser(ISessionManager sessionManager, OAuth2WebBrowserOptions options)\n        {\n            EnsureArgument.NotNull(sessionManager, nameof(sessionManager));\n            EnsureArgument.NotNull(options, nameof(options));\n\n            _sessionManager = sessionManager;\n            _options = options;\n        }\n\n        public Uri UpdateRedirectUri(Uri uri)\n        {\n            if (!uri.IsLoopback)\n            {\n                throw new ArgumentException(\"Only localhost is supported as a redirect URI.\", nameof(uri));\n            }\n\n            // If a port has been specified use it, otherwise find a free one\n            if (uri.IsDefaultPort)\n            {\n                int port = GetFreeTcpPort();\n                return new UriBuilder(uri) {Port = port}.Uri;\n            }\n\n            return uri;\n        }\n\n        public async Task<IDictionary<string, string>> GetAuthenticationResponseAsync(\n            Uri authorizationUri, Uri redirectUri, OAuth2ResponseMode responseMode, CancellationToken ct)\n        {\n            if (!redirectUri.IsLoopback)\n            {\n                throw new ArgumentException(\"Only localhost is supported as a redirect URI.\", nameof(redirectUri));","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/git-ecosystem/git-credential-manager/blob/e8ce762cd04b4100ae637b5fbf39ef9d0a96561e/src/Core/Authentication/OAuth/OAuth2SystemWebBrowser.cs#L67-L103","documentation":"OAuth2SystemWebBrowser.UpdateRedirectUri rewrites the redirect URI to a loopback address with a free TCP port so the embedded system browser can intercept the authorization response. It throws ArgumentException because only loopback (localhost/127.0.0.1) redirect URIs can be intercepted locally.","triggerScenarios":"Calling UpdateRedirectUri with a Uri whose IsLoopback is false — e.g. https://myapp.example.com/callback, a public hostname, or a non-loopback IP.","commonSituations":"Registering an OAuth app with an https production redirect URI and reusing it in a desktop flow; copying a web-app redirect URI into the CLI config; misunderstanding that this browser implementation only supports local loopback interception.","solutions":["Use a loopback redirect URI such as http://localhost/callback (port is optional; a free port is chosen automatically)","Register http://localhost as an allowed redirect URI in your OAuth provider's app settings","Use a different browser/flow (e.g. device code flow) when a non-loopback redirect is required"],"exampleFix":"// before\nbrowser.UpdateRedirectUri(new Uri(\"https://myapp.example.com/callback\"));\n// after\nbrowser.UpdateRedirectUri(new Uri(\"http://localhost/callback\"));","handlingStrategy":"validation","validationCode":"if (!redirectUri.IsLoopback) throw new ArgumentException(\"Browser flow requires a loopback redirect URI; use device code flow instead.\", nameof(redirectUri));","typeGuard":"bool IsLoopbackRedirect(Uri u) => u.IsLoopback;","tryCatchPattern":"try { uri = browser.UpdateRedirectUri(uri); } catch (ArgumentException) { return await GetTokenByDeviceCodeAsync(client, scopes); }","preventionTips":["Always use http://localhost[...]/ as redirect URI for desktop flows","Register the localhost redirect URI with your OAuth provider","Check uri.IsLoopback before invoking any OAuth2SystemWebBrowser API"],"tags":["oauth2","redirect-uri","localhost","argument"],"backgroundTag":"invalid-argument-value","analyzedSha":"e8ce762cd04b4100ae637b5fbf39ef9d0a96561e","analyzedAt":"2026-09-11T17:15:08.753Z","contentChangedAt":"2026-09-11T17:15:08.753Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}