{"record":{"id":"5cb449a40f77ecb7","repo":"bitwarden/server","slug":"invalidreturnurl","errorCode":null,"errorMessage":"InvalidReturnUrl","messagePattern":"InvalidReturnUrl","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"bitwarden_license/src/Sso/Controllers/AccountController.cs","lineNumber":220,"sourceCode":"            ssoToken\n        });\n    }\n\n    [HttpGet]\n    public IActionResult ExternalChallenge(string scheme, string returnUrl, string state, string userIdentifier, string ssoToken)\n    {\n        ValidateSchemeAgainstSsoToken(scheme, ssoToken);\n\n        if (string.IsNullOrEmpty(returnUrl))\n        {\n            returnUrl = \"~/\";\n        }\n\n        // Clean the returnUrl\n        returnUrl = CoreHelpers.ReplaceWhiteSpace(returnUrl, string.Empty);\n        if (!Url.IsLocalUrl(returnUrl) && !_interaction.IsValidReturnUrl(returnUrl))\n        {\n            throw new Exception(_i18nService.T(\"InvalidReturnUrl\"));\n        }\n\n        var props = new AuthenticationProperties\n        {\n            RedirectUri = Url.Action(nameof(ExternalCallback)),\n            Items =\n            {\n                // scheme will get serialized into `State` and returned back\n                { \"scheme\", scheme },\n                { \"return_url\", returnUrl },\n                { \"state\", state },\n                { \"user_identifier\", userIdentifier },\n            }\n        };\n\n        return Challenge(props, scheme);\n    }\n","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/bitwarden_license/src/Sso/Controllers/AccountController.cs#L202-L238","documentation":"Thrown in AccountController.ExternalChallenge (line 220) when returnUrl, after whitespace cleaning, is neither a local URL (Url.IsLocalUrl) nor recognized as valid by IdentityServer (_interaction.IsValidReturnUrl). This is an open-redirect and tamper guard.","triggerScenarios":"ExternalChallenge is invoked with a returnUrl pointing to an external host or a malformed/internal path that IdentityServer does not consider valid.","commonSituations":"Return URL was manually edited or crafted to point outside the application; client passed an absolute URL instead of a relative path; IdentityServer client configuration doesn't include the redirect URI; returnUrl contains encoded whitespace that survives cleaning.","solutions":["Pass only relative/local return URLs (starting with ~/ or /) to ExternalChallenge.","Register the redirect URI in IdentityServer client configuration if it must be absolute.","Verify the returnUrl originates from the legitimate authorize flow and hasn't been tampered with."],"exampleFix":"// before — absolute external URL fails validation\nvar returnUrl = \"https://external.example.com/callback\";\n// after — use a local relative path registered with IdentityServer\nvar returnUrl = \"~/sso/callback\";","handlingStrategy":"validation","validationCode":"returnUrl = CoreHelpers.ReplaceWhiteSpace(returnUrl, string.Empty);\nif (!Url.IsLocalUrl(returnUrl) && !_interaction.IsValidReturnUrl(returnUrl))\n    return BadRequest(\"The return URL is not valid.\");","typeGuard":null,"tryCatchPattern":"try { return ExternalChallenge(scheme, returnUrl, state, userIdentifier, ssoToken); }\ncatch (Exception ex) when (ex.Message.Contains(\"InvalidReturnUrl\"))\n{ /* redirect to home with error toast */ }","preventionTips":["Use only relative return URLs (~/path or /path) in SSO flows.","Register absolute redirect URIs in IdentityServer client config if needed.","Never accept returnUrl from untrusted user input without validation."],"tags":["sso","authentication","url-validation","open-redirect","security","identity-server"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}