CVE-2021-43812: URL Redirection to Untrusted Site ('Open Redirect') (CWE-601)
value: The Auth0 Next.js SDK is a library for implementing user authentication in Next.js applications. Versions before 1.6.2 do not filter out certain returnTo parameter values from the login url, which expose the application to an open redirect vulnerability. Users are advised to upgrade as soon as possible. There are no known workarounds for this issue.
CWE description: A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks.
CVSS v3 base score: 6.1
Published: 2021-12-16T19:15Z
Repository: https://github.com/auth0/nextjs-auth0
Language: TypeScript
Files changed: src/handlers/login.ts, src/index.ts, src/utils/url-helpers.ts, tests/fixtures/open-redirect-payloads.json, tests/handlers/login.test.ts, tests/utils/url-helpers.test.ts
Commit date: 2021-12-15 13:24:33 -0300
Commit message: Enforce configured host on user supplied returnTo (#557)

* Enforce configured host on user supplied returnTo

* Test improvements per PR suggestions
Diff: diff --git a/src/handlers/login.ts b/src/handlers/login.ts
index ed24eeb..21e02a8 100644
--- a/src/handlers/login.ts
+++ b/src/handlers/login.ts
@@ -1,10 +1,10 @@
 import { NextApiResponse, NextApiRequest } from 'next';
 import { AuthorizationParameters, HandleLogin as BaseHandleLogin } from '../auth0-session';
-import isSafeRedirect from '../utils/url-helpers';
+import toSafeRedirect from '../utils/url-helpers';
 import { assertReqRes } from '../utils/assert';
-import { NextConfig } from '../config';
+import { BaseConfig, NextConfig } from '../config';
 import { HandlerError } from '../utils/errors';
 
 /**
  * Use this to store additional state for the user before they visit the Identity Provider to login.
  *
@@ -115,20 +115,23 @@ export interface LoginOptions {
 export type HandleLogin = (req: NextApiRequest, res: NextApiResponse, options?: LoginOptions) => Promise<void>;
 
 /**
  * @ignore
  */
-export default function handleLoginFactory(handler: BaseHandleLogin, nextConfig: NextConfig): HandleLogin {
+export default function handleLoginFactory(
+  handler: BaseHandleLogin,
+  nextConfig: NextConfig,
+  baseConfig: BaseConfig
+): HandleLogin {
   return async (req, res, options = {}): Promise<void> => {
     try {
       assertReqRes(req, res);
       if (req.query.returnTo) {
-        const returnTo = Array.isArray(req.query.returnTo) ? req.query.returnTo[0] : req.query.returnTo;
+        const dangerousReturnTo = Array.isArray(req.query.returnTo) ? req.query.returnTo[0] : req.query.returnTo;
+        const safeBaseUrl = new URL(options.authorizationParams?.redirect_uri || baseConfig.baseURL);
 
-        if (!isSafeRedirect(returnTo)) {
-          throw new Error('Invalid value provided for returnTo, must be a relative url');
-        }
+        const returnTo = toSafeRedirect(dangerousReturnTo, safeBaseUrl);
 
         options = { ...options, returnTo };
       }
       if (nextConfig.organization) {
         options = {
diff --git a/src/index.ts b/src/index.ts
index baf7731..22e4405 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -75,11 +75,11 @@ export const initAuth0: InitAuth0 = (params) => {
   // Init Next layer (with next config)
   const getSession = sessionFactory(sessionCache);
   const getAccessToken = accessTokenFactory(nextConfig, getClient, sessionCache);
   const withApiAuthRequired = withApiAuthRequiredFactory(sessionCache);
   const withPageAuthRequired = withPageAuthRequiredFactory(nextConfig.routes.login, getSession);
-  const handleLogin = loginHandler(baseHandleLogin, nextConfig);
+  const handleLogin = loginHandler(baseHandleLogin, nextConfig, baseConfig);
   const handleLogout = logoutHandler(baseHandleLogout);
   const handleCallback = callbackHandler(baseHandleCallback, nextConfig);
   const handleProfile = profileHandler(getClient, getAccessToken, sessionCache);
   const handleAuth = handlerFactory({ handleLogin, handleLogout, handleCallback, handleProfile });
 
diff --git a/src/utils/url-helpers.ts b/src/utils/url-helpers.ts
index ac0fbcf..32e5b4b 100644
--- a/src/utils/url-helpers.ts
+++ b/src/utils/url-helpers.ts
@@ -1,16 +1,17 @@
 /**
  * Helper which tests if a URL can safely be redirected to. Requires the URL to be relative.
- * @param url
+ * @param dangerousRedirect
+ * @param safeBaseUrl
  */
-export default function isSafeRedirect(url: string): boolean {
-  if (typeof url !== 'string') {
-    throw new TypeError(`Invalid url: ${url}`);
+export default function toSafeRedirect(dangerousRedirect: string, safeBaseUrl: URL): string | undefined {
+  let url: URL;
+  try {
+    url = new URL(dangerousRedirect, safeBaseUrl);
+  } catch (e) {
+    return undefined;
   }
-
-  // Prevent open redirects using the //foo.com format (double forward slash).
-  if (/\/\//.test(url)) {
-    return false;
+  if (url.origin === safeBaseUrl.origin) {
+    return url.toString();
   }
-
-  return !/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url);
+  return undefined;
 }
diff --git a/tests/fixtures/open-redirect-payloads.json b/tests/fixtures/open-redirect-payloads.json
new file mode 100644
index 0000000..1bb1421
--- /dev/null
+++ b/tests/fixtures/open-redirect-payloads.json
@@ -0,0 +1,487 @@
+[
+  "javascript:alert(1)",
+  "java%0d%0ascript%0d%0a:alert(0)",
+  "javascript://%250Aalert(1)",
+  "javascript://%250Aalert(1)//?1",
+  "javascript://%250A1?alert(1):0",
+  "%09Jav%09ascript:alert(document.domain)",
+  "javascript://%250Alert(document.location=document.cookie)",
+  "/%09/javascript:alert(1);",
+  "/%09/javascript:alert(1)",
+  "//%5cjavascript:alert(1);",
+  "//%5cjavascript:alert(1)",
+  "/%5cjavascript:alert(1);",
+  "/%5cjavascript:alert(1)",
+  "javascript://%0aalert(1)",
+  "<>javascript:alert(1);",
+  "//javascript:alert(1);",
+  "//javascript:alert(1)",
+  "/javascript:alert(1);",
+  "/javascript:alert(1)",
+  "javascript:alert(1)",
+  "javascript:alert(1);",
+  "javascript:alert(1)",
+  "javascripT://anything%0D%0A%0D%0Awindow.alert(document.cookie)",
+  "javascript:confirm(1)",
+  "javascript://https://whitelisted.com/?z=%0Aalert(1)",
+  "javascript:prompt(1)",
+  "jaVAscript://whitelisted.com//%0d%0aalert(1);//",
+  "javascript://whitelisted.com?%a0alert%281%29",
+  "/x:1/:///%01javascript:alert(document.cookie)/",
+  "<>//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "//;@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "/////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "///;@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/.Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "/〱Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  ".Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "〱Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ%00｡Ｐⓦ",
+  "%01https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "%01https://google.com",
+  "////%09/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "///%09/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "//%09/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/%09/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "////%09/google.com",
+  "///%09/google.com",
+  "//%09/google.com",
+  "/%09/google.com",
+  "////%09/whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "///%09/whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "//%09/whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/%09/whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "////%09/whitelisted.com@google.com",
+  "///%09/whitelisted.com@google.com",
+  "//%09/whitelisted.com@google.com",
+  "/%09/whitelisted.com@google.com",
+  "&%0d%0a1Location:https://google.com",
+  "%19Jav%09asc%09ript:https%20://whitelisted.com/%250Aconfirm%25281%2529",
+  "////216.58.214.206",
+  "///216.58.214.206",
+  "//216.58.214.206",
+  "/\\216.58.214.206",
+  "/216.58.214.206",
+  "216.58.214.206",
+  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "%2f216.58.214.206//",
+  "%2f216.58.214.206",
+  "%2f216.58.214.206%2f%2f",
+  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "//%2f%2fⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/%2f%2fⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "%2f$2f216.58.214.206",
+  "$2f%2f216.58.214.206%2f%2f",
+  "%2f$2f3627734734",
+  "$2f%2f3627734734%2f%2f",
+  "//%2f%2fgoogle.com",
+  "/%2f%2fgoogle.com",
+  "$2f%2fgoogle.com",
+  "%2f$2fgoogle.com",
+  "$2f%2fgoogle.com%2f%2f",
+  "%2f3627734734//",
+  "%2f3627734734",
+  "%2f3627734734%2f%2f",
+  "/%2f%5c%2f%67%6f%6f%67%6c%65%2e%63%6f%6d/",
+  "/%2f%5c%2f%6c%6f%63%61%6c%64%6f%6d%61%69%6e%2e%70%77/",
+  "%2fgoogle.com//",
+  "%2fgoogle.com",
+  "%2fgoogle.com%2f%2f",
+  "////3627734734",
+  "///3627734734",
+  "//3627734734",
+  "/3627734734",
+  "3627734734",
+  "//3H6k7lIAiqjfNeN@whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "//3H6k7lIAiqjfNeN@whitelisted.com+@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "//3H6k7lIAiqjfNeN@whitelisted.com@google.com/",
+  "//3H6k7lIAiqjfNeN@whitelisted.com+@google.com/",
+  "////%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "///%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "//%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "////%5cgoogle.com",
+  "///%5cgoogle.com",
+  "//%5cgoogle.com",
+  "/%5cgoogle.com",
+  "////%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "///%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "//%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "////%5cwhitelisted.com@google.com",
+  "///%5cwhitelisted.com@google.com",
+  "//%5cwhitelisted.com@google.com",
+  "/%5cwhitelisted.com@google.com",
+  "/%68%74%74%70%3a%2f%2f%67%6f%6f%67%6c%65%2e%63%6f%6d",
+  "%68%74%74%70%3a%2f%2f%67%6f%6f%67%6c%65%2e%63%6f%6d",
+  "%68%74%74%70%73%3a%2f%2f%6c%6f%63%61%6c%64%6f%6d%61%69%6e%2e%70%77",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ:80?@whitelisted.com/",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ:80#@whitelisted.com/",
+  "\";alert(0);//",
+  "data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=",
+  "data:text/html;base64,PHNjcmlwdD5hbGVydCgiWFNTIik7PC9zY3JpcHQ+Cg==",
+  "data:text/html;base64,PHNjcmlwdD5hbGVydCgiWFNTIik8L3NjcmlwdD4=",
+  "data:whitelisted.com;text/html;charset=UTF-8,<html><script>document.write(document.domain);</script><iframe/src=xxxxx>aaaa</iframe></html>",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ%E3%80%82pw",
+  "//google%00.com",
+  "/google%252ecom",
+  "google%252ecom",
+  "<>//google.com",
+  "/<>//google.com",
+  "//;@google.com",
+  "///;@google.com",
+  "/////google.com/",
+  "/////google.com",
+  "////;@google.com",
+  "////google.com//",
+  "////google.com/",
+  "////google.com",
+  "///;@google.com",
+  "///google.com//",
+  "///google.com/",
+  "///google.com",
+  "///google.com/",
+  "//google.com",
+  "//google.com//",
+  "//google.com/",
+  "//google.com",
+  "/.google.com",
+  "///google.com/",
+  "//google.com/",
+  "//google.com",
+  "/google.com",
+  "/〱google.com",
+  "/google.com",
+  "../google.com",
+  ".google.com",
+  "@google.com",
+  "//google.com/",
+  "〱google.com",
+  "google.com",
+  "google.com%23@whitelisted.com",
+  "////google.com/%2e%2e",
+  "///google.com/%2e%2e",
+  "//google.com/%2e%2e",
+  "/google.com/%2e%2e",
+  "//google.com/%2E%2E",
+  "////google.com/%2e%2e%2f",
+  "///google.com/%2e%2e%2f",
+  "//google.com/%2e%2e%2f",
+  "////google.com/%2f..",
+  "///google.com/%2f..",
+  "//google.com/%2f..",
+  "//google.com/%2F..",
+  "/google.com/%2F..",
+  "////google.com/%2f%2e%2e",
+  "///google.com/%2f%2e%2e",
+  "//google.com/%2f%2e%2e",
+  "/google.com/%2f%2e%2e",
+  "//google.com//%2F%2E%2E",
+  "//google.com:80?@whitelisted.com/",
+  "//google.com:80#@whitelisted.com/",
+  "google.com/.jpg",
+  "//google.com\twhitelisted.com/",
+  "//google.com/whitelisted.com",
+  "//google.com@whitelisted.com",
+  "google.com/whitelisted.com",
+  "//google%E3%80%82com",
+  "/http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/http:/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "http://;@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "http://.Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "http:/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "http:Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "http://00330.00072.0000326.00000316",
+  "http:00330.00072.0000326.00000316",
+  "http://00330.0x3a.54990",
+  "http:00330.0x3a.54990",
+  "http://00330.3856078",
+  "http:00330.3856078",
+  "http://0330.072.0326.0316",
+  "http:0330.072.0326.0316",
+  "http:%0a%0dⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "http:%0a%0dgoogle.com",
+  "http://0xd8.072.54990",
+  "http:0xd8.072.54990",
+  "http://0xd8.0x3a.0xd6.0xce",
+  "http:0xd8.0x3a.0xd6.0xce",
+  "http://0xd8.3856078",
+  "http:0xd8.3856078",
+  "http://0xd83ad6ce",
+  "http:0xd83ad6ce",
+  "http://[::216.58.214.206]",
+  "http:[::216.58.214.206]",
+  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ%23.whitelisted.com/",
+  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ%2f%2f.whitelisted.com/",
+  "http://3627734734",
+  "http:3627734734",
+  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ%3F.whitelisted.com/",
+  "http://3H6k7lIAiqjfNeN@00330.00072.0000326.00000316",
+  "http:3H6k7lIAiqjfNeN@00330.00072.0000326.00000316",
+  "http://3H6k7lIAiqjfNeN@00330.0x3a.54990",
+  "http:3H6k7lIAiqjfNeN@00330.0x3a.54990",
+  "http://3H6k7lIAiqjfNeN@00330.3856078",
+  "http:3H6k7lIAiqjfNeN@00330.3856078",
+  "http://3H6k7lIAiqjfNeN@0330.072.0326.0316",
+  "http:3H6k7lIAiqjfNeN@0330.072.0326.0316",
+  "http://3H6k7lIAiqjfNeN@0xd8.072.54990",
+  "http:3H6k7lIAiqjfNeN@0xd8.072.54990",
+  "http://3H6k7lIAiqjfNeN@0xd8.0x3a.0xd6.0xce",
+  "http:3H6k7lIAiqjfNeN@0xd8.0x3a.0xd6.0xce",
+  "http://3H6k7lIAiqjfNeN@0xd8.3856078",
+  "http:3H6k7lIAiqjfNeN@0xd8.3856078",
+  "http://3H6k7lIAiqjfNeN@0xd83ad6ce",
+  "http:3H6k7lIAiqjfNeN@0xd83ad6ce",
+  "http://3H6k7lIAiqjfNeN@[::216.58.214.206]",
+  "http:3H6k7lIAiqjfNeN@[::216.58.214.206]",
+  "http://3H6k7lIAiqjfNeN@3627734734",
+  "http:3H6k7lIAiqjfNeN@3627734734",
+  "http://3H6k7lIAiqjfNeN@472.314.470.462",
+  "http:3H6k7lIAiqjfNeN@472.314.470.462",
+  "http://3H6k7lIAiqjfNeN@[::ffff:216.58.214.206]",
+  "http:3H6k7lIAiqjfNeN@[::ffff:216.58.214.206]",
+  "http://3H6k7lIAiqjfNeN@whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "http://3H6k7lIAiqjfNeN@whitelisted.com+@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "http://3H6k7lIAiqjfNeN@whitelisted.com@google.com/",
+  "http://3H6k7lIAiqjfNeN@whitelisted.com+@google.com/",
+  "http://472.314.470.462",
+  "http:472.314.470.462",
+  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ%5c%5c.whitelisted.com/",
+  "/http://%67%6f%6f%67%6c%65%2e%63%6f%6d",
+  "http://%67%6f%6f%67%6c%65%2e%63%6f%6d",
+  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ:80?@whitelisted.com/",
+  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ:80#@whitelisted.com/",
+  "http://[::ffff:216.58.214.206]",
+  "http:[::ffff:216.58.214.206]",
+  "/http://google.com",
+  "/http:/google.com",
+  "http://;@google.com",
+  "http://.google.com",
+  "http://google.com",
+  "http://google.com",
+  "http:/google.com",
+  "http:google.com",
+  "http://google.com%23.whitelisted.com/",
+  "http://google.com%2f%2f.whitelisted.com/",
+  "http://google.com%3F.whitelisted.com/",
+  "http://google.com%5c%5c.whitelisted.com/",
+  "http://google.com:80?@whitelisted.com/",
+  "http://google.com:80#@whitelisted.com/",
+  "http://google.com\twhitelisted.com/",
+  "//https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "/https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "https:Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "https://%09/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/https://%09/google.com",
+  "https://%09/google.com",
+  "https://%09/whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "https://%09/whitelisted.com@google.com",
+  "https://%0a%0dⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "https://%0a%0dgoogle.com",
+  "//https:///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "/https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "https:///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "//https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "/https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "/https:///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "/https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "https:///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "https%3a%2f%2fgoogle.com%2f",
+  "/https://%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/https:/%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "https://%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "https:/%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "/https://%5cgoogle.com",
+  "/https:/%5cgoogle.com/",
+  "https://%5cgoogle.com",
+  "https:/%5cgoogle.com/",
+  "/https://%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "https://%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/https://%5cwhitelisted.com@google.com",
+  "https://%5cwhitelisted.com@google.com",
+  "https://%6c%6f%63%61%6c%64%6f%6d%61%69%6e%2e%70%77",
+  "//https://google.com//",
+  "/https://google.com//",
+  "/https://google.com/",
+  "/https://google.com",
+  "/https:google.com",
+  "https://////google.com",
+  "https://google.com//",
+  "https://google.com/",
+  "https://google.com",
+  "https:/google.com",
+  "https:google.com",
+  "//https:///google.com/%2e%2e",
+  "/https://google.com/%2e%2e",
+  "https:///google.com/%2e%2e",
+  "//https://google.com/%2e%2e%2f",
+  "https://google.com/%2e%2e%2f",
+  "/https://google.com/%2f..",
+  "https://google.com/%2f..",
+  "/https:///google.com/%2f%2e%2e",
+  "/https://google.com/%2f%2e%2e",
+  "https:///google.com/%2f%2e%2e",
+  "https://google.com/%2f%2e%2e",
+  "https://:@google.com@whitelisted.com",
+  "https://google.com?whitelisted.com",
+  "https://google.com/whitelisted.com",
+  "https://google.comwhitelisted.com",
+  "https://google.com#whitelisted.com",
+  "https://google%E3%80%82com",
+  "//https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "/https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "https://:@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ@whitelisted.com",
+  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/whitelisted.com",
+  "https://whitelisted.com;@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "/https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "https:///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "//https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "/https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "/https:///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "/https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "https:///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "//https://whitelisted.com@google.com//",
+  "/https://whitelisted.com@google.com/",
+  "https://whitelisted.com;@google.com",
+  "https://whitelisted.com.google.com",
+  "https://whitelisted.com@google.com//",
+  "https://whitelisted.com@google.com/",
+  "https://whitelisted.com@google.com",
+  "/https://whitelisted.com@google.com/%2e%2e",
+  "https:///whitelisted.com@google.com/%2e%2e",
+  "//https://whitelisted.com@google.com/%2e%2e%2f",
+  "https://whitelisted.com@google.com/%2e%2e%2f",
+  "/https://whitelisted.com@google.com/%2f..",
+  "https://whitelisted.com@google.com/%2f..",
+  "/https:///whitelisted.com@google.com/%2f%2e%2e",
+  "/https://whitelisted.com@google.com/%2f%2e%2e",
+  "https:///whitelisted.com@google.com/%2f%2e%2e",
+  "https://whitelisted.com@google.com/%2f%2e%2e",
+  "/https://whitelisted.com@google.com/%2f.//whitelisted.com@google.com/%2f..",
+  "https://whitelisted.com/https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "https://whitelisted.com/https://google.com/",
+  "@https://www.google.com",
+  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ\twhitelisted.com/",
+  "http://whitelisted.com@00330.00072.0000326.00000316",
+  "http:whitelisted.com@00330.00072.0000326.00000316",
+  "http://whitelisted.com@00330.0x3a.54990",
+  "http:whitelisted.com@00330.0x3a.54990",
+  "http://whitelisted.com@00330.3856078",
+  "http:whitelisted.com@00330.3856078",
+  "http://whitelisted.com@0330.072.0326.0316",
+  "http:whitelisted.com@0330.072.0326.0316",
+  "http://whitelisted.com@0xd8.072.54990",
+  "http:whitelisted.com@0xd8.072.54990",
+  "http://whitelisted.com@0xd8.0x3a.0xd6.0xce",
+  "http:whitelisted.com@0xd8.0x3a.0xd6.0xce",
+  "http://whitelisted.com@0xd8.3856078",
+  "http:whitelisted.com@0xd8.3856078",
+  "http://whitelisted.com@0xd83ad6ce",
+  "http:whitelisted.com@0xd83ad6ce",
+  "http://whitelisted.com@[::216.58.214.206]",
+  "http:whitelisted.com@[::216.58.214.206]",
+  "http://whitelisted.com%2eⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "http://whitelisted.com%2egoogle.com/",
+  "http://whitelisted.com@3627734734",
+  "http:whitelisted.com@3627734734",
+  "http://whitelisted.com@472.314.470.462",
+  "http:whitelisted.com@472.314.470.462",
+  "http://whitelisted.com:80%40Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "http://whitelisted.com:80%40google.com/",
+  "http://whitelisted.com@[::ffff:216.58.214.206]",
+  "http:whitelisted.com@[::ffff:216.58.214.206]",
+  "http://whitelisted.com@google.com/",
+  "http://whitelisted.com+&@google.com#+@whitelisted.com/",
+  "http://whitelisted.com+&@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ#+@whitelisted.com/",
+  "http://www.google.com.whitelisted.com",
+  "http://www.Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ.whitelisted.com",
+  "ja\nva\tscript\r:alert(1)",
+  "java%09script:alert(1)",
+  "java%0ascript:alert(1)",
+  "java%0d%0ascript%0d%0a:alert(0)",
+  "java%0dscript:alert(1)",
+  "Javas%26%2399;ript:alert(1)",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ\twhitelisted.com/",
+  "\u006A\u0061\u0076\u0061\u0073\u0063\u0072\u0069\u0070\u0074\u003aalert(1)",
+  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/whitelisted.com",
+  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ@whitelisted.com",
+  "//whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
+  "//whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
+  "Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/whitelisted.com",
+  "whitelisted.com;@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
+  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "//whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
+  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "//whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
+  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "//whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
+  "/whitelisted.com:80%40google.com",
+  "whitelisted.com@%E2%80%AE@google.com",
+  "////whitelisted.com@google.com//",
+  "////whitelisted.com@google.com/",
+  "///whitelisted.com@google.com//",
+  "///whitelisted.com@google.com/",
+  "//whitelisted.com@google.com//",
+  "//whitelisted.com@google.com/",
+  "whitelisted.com;@google.com",
+  "whitelisted.com.google.com",
+  "////whitelisted.com@google.com/%2e%2e",
+  "///whitelisted.com@google.com/%2e%2e",
+  "////whitelisted.com@google.com/%2e%2e%2f",
+  "///whitelisted.com@google.com/%2e%2e%2f",
+  "//whitelisted.com@google.com/%2e%2e%2f",
+  "////whitelisted.com@google.com/%2f..",
+  "///whitelisted.com@google.com/%2f..",
+  "//whitelisted.com@google.com/%2f..",
+  "////whitelisted.com@google.com/%2f%2e%2e",
+  "///whitelisted.com@google.com/%2f%2e%2e",
+  "//whitelisted.com@google.com/%2f%2e%2e",
+  "//whitelisted.com+&@google.com#+@whitelisted.com/",
+  "//whitelisted.com@https:///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
+  "//whitelisted.com@https:///google.com/%2e%2e",
+  "//whitelisted.com+&@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ#+@whitelisted.com/"
+]
diff --git a/tests/handlers/login.test.ts b/tests/handlers/login.test.ts
index 88bc9e1..bf9c97a 100644
--- a/tests/handlers/login.test.ts
+++ b/tests/handlers/login.test.ts
@@ -212,11 +212,11 @@ describe('login handler', () => {
     await get(baseUrl, '/api/auth/login?returnTo=/foo', { cookieJar });
     const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie;
 
     const decodedState = decodeState(state.split('.')[0]);
     expect(decodedState).toEqual({
-      returnTo: '/foo'
+      returnTo: new URL('/foo', withoutApi.baseURL).toString()
     });
   });
 
   test('should take the first returnTo url provided in the querystring', async () => {
     const loginOptions = {
@@ -227,23 +227,61 @@ describe('login handler', () => {
     await get(baseUrl, '/api/auth/login?returnTo=/foo&returnTo=/bar', { cookieJar });
     const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie;
 
     const decodedState = decodeState(state.split('.')[0]);
     expect(decodedState).toEqual({
-      returnTo: '/foo'
+      returnTo: new URL('/foo', withoutApi.baseURL).toString()
     });
   });
 
   test('should not allow absolute urls to be provided in the querystring', async () => {
     const loginOptions = {
       returnTo: '/default-redirect'
     };
     const baseUrl = await setup(withoutApi, { loginOptions });
 
-    await expect(
-      get(baseUrl, '/api/auth/login?returnTo=https://www.google.com', { fullResponse: true })
-    ).rejects.toThrow('Invalid value provided for returnTo, must be a relative url');
+    const cookieJar = new CookieJar();
+    await get(baseUrl, '/api/auth/login?returnTo=https://www.google.com', { cookieJar });
+    const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie;
+
+    const decodedState = decodeState(state.split('.')[0]);
+    expect(decodedState).toEqual({});
+  });
+
+  test('should allow absolute urls in params of returnTo urls', async () => {
+    const loginOptions = {
+      returnTo: '/default-redirect'
+    };
+    const baseUrl = await setup(withoutApi, { loginOptions });
+
+    const cookieJar = new CookieJar();
+    await get(baseUrl, '/api/auth/login?returnTo=/foo?url=https://www.google.com', { cookieJar });
+    const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie;
+
+    const decodedState = decodeState(state.split('.')[0]);
+    expect(decodedState).toEqual({
+      returnTo: new URL('/foo?url=https://www.google.com', withoutApi.baseURL).toString()
+    });
+  });
+
+  test('should redirect relative to the redirect_uri over the base url', async () => {
+    const loginOptions = {
+      returnTo: '/default-redirect',
+      authorizationParams: {
+        redirect_uri: 'https://other-org.acme.com/api/auth/callback'
+      }
+    };
+    const baseUrl = await setup(withoutApi, { loginOptions });
+
+    const cookieJar = new CookieJar();
+    await get(baseUrl, '/api/auth/login?returnTo=/foo', { cookieJar });
+    const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie;
+
+    const decodedState = decodeState(state.split('.')[0]);
+    expect(decodedState).toEqual({
+      returnTo: 'https://other-org.acme.com/foo'
+    });
   });
 
   test('should escape html in errors', async () => {
     const baseUrl = await setup(withoutApi, { discoveryOptions: { error: '<script>alert("xss")</script>' } });
 
diff --git a/tests/utils/url-helpers.test.ts b/tests/utils/url-helpers.test.ts
index 50b7a8c..0cf719b 100644
--- a/tests/utils/url-helpers.test.ts
+++ b/tests/utils/url-helpers.test.ts
@@ -1,25 +1,34 @@
-import isSafeRedirect from '../../src/utils/url-helpers';
+import toSafeRedirect from '../../src/utils/url-helpers';
+import payloads from '../fixtures/open-redirect-payloads.json';
 
 describe('url-helpers', () => {
+  const safeBaseUrl = new URL('http://www.example.com');
+
   describe('isSafeRedirect', () => {
     test('should not allow absolute urls', () => {
-      expect(isSafeRedirect('file://foo')).toEqual(false);
-      expect(isSafeRedirect('https://foo')).toEqual(false);
-      expect(isSafeRedirect('http://foo')).toEqual(false);
+      expect(toSafeRedirect('file://foo', safeBaseUrl)).toEqual(undefined);
+      expect(toSafeRedirect('https://foo', safeBaseUrl)).toEqual(undefined);
+      expect(toSafeRedirect('http://foo', safeBaseUrl)).toEqual(undefined);
     });
 
     test('should allow relative urls', () => {
-      expect(isSafeRedirect('/foo')).toEqual(true);
-      expect(isSafeRedirect('/foo?some=value')).toEqual(true);
+      expect(toSafeRedirect('/foo', safeBaseUrl)).toEqual('http://www.example.com/foo');
+      expect(toSafeRedirect('foo', safeBaseUrl)).toEqual('http://www.example.com/foo');
+      expect(toSafeRedirect('/foo?some=value', safeBaseUrl)).toEqual('http://www.example.com/foo?some=value');
+      expect(toSafeRedirect('/foo?someUrl=https://www.google.com', safeBaseUrl)).toEqual(
+        'http://www.example.com/foo?someUrl=https://www.google.com'
+      );
+      expect(toSafeRedirect('/foo', new URL('http://www.example.com:8888'))).toEqual('http://www.example.com:8888/foo');
     });
 
     test('should prevent open redirects', () => {
-      expect(isSafeRedirect('//google.com')).toEqual(false);
-      expect(isSafeRedirect('///google.com')).toEqual(false);
+      for (const payload of payloads) {
+        expect(toSafeRedirect(payload, safeBaseUrl) || safeBaseUrl.toString()).toMatch(/^http:\/\/www.example.com\//);
+      }
     });
 
-    test('should throw when non string provided', () => {
-      expect(isSafeRedirect.bind(null)).toThrow('Invalid url: undefined');
+    test('should not throw for empty redirect', () => {
+      expect(toSafeRedirect.bind(null, '', safeBaseUrl)).not.toThrow();
     });
   });
 });

Vulnerable code: import isSafeRedirect from '../utils/url-helpers';
import { NextConfig } from '../config';
export default function handleLoginFactory(handler: BaseHandleLogin, nextConfig: NextConfig): HandleLogin {
        const returnTo = Array.isArray(req.query.returnTo) ? req.query.returnTo[0] : req.query.returnTo;
        if (!isSafeRedirect(returnTo)) {
          throw new Error('Invalid value provided for returnTo, must be a relative url');
        }
  const handleLogin = loginHandler(baseHandleLogin, nextConfig);
 * @param url
export default function isSafeRedirect(url: string): boolean {
  if (typeof url !== 'string') {
    throw new TypeError(`Invalid url: ${url}`);

  // Prevent open redirects using the //foo.com format (double forward slash).
  if (/\/\//.test(url)) {
    return false;

  return !/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url);
      returnTo: '/foo'
      returnTo: '/foo'
    await expect(
      get(baseUrl, '/api/auth/login?returnTo=https://www.google.com', { fullResponse: true })
    ).rejects.toThrow('Invalid value provided for returnTo, must be a relative url');
import isSafeRedirect from '../../src/utils/url-helpers';
      expect(isSafeRedirect('file://foo')).toEqual(false);
      expect(isSafeRedirect('https://foo')).toEqual(false);
      expect(isSafeRedirect('http://foo')).toEqual(false);
      expect(isSafeRedirect('/foo')).toEqual(true);
      expect(isSafeRedirect('/foo?some=value')).toEqual(true);
      expect(isSafeRedirect('//google.com')).toEqual(false);
      expect(isSafeRedirect('///google.com')).toEqual(false);
    test('should throw when non string provided', () => {
      expect(isSafeRedirect.bind(null)).toThrow('Invalid url: undefined');
Fixed code: import toSafeRedirect from '../utils/url-helpers';
import { BaseConfig, NextConfig } from '../config';
export default function handleLoginFactory(
  handler: BaseHandleLogin,
  nextConfig: NextConfig,
  baseConfig: BaseConfig
): HandleLogin {
        const dangerousReturnTo = Array.isArray(req.query.returnTo) ? req.query.returnTo[0] : req.query.returnTo;
        const safeBaseUrl = new URL(options.authorizationParams?.redirect_uri || baseConfig.baseURL);
        const returnTo = toSafeRedirect(dangerousReturnTo, safeBaseUrl);
  const handleLogin = loginHandler(baseHandleLogin, nextConfig, baseConfig);
 * @param dangerousRedirect
 * @param safeBaseUrl
export default function toSafeRedirect(dangerousRedirect: string, safeBaseUrl: URL): string | undefined {
  let url: URL;
  try {
    url = new URL(dangerousRedirect, safeBaseUrl);
  } catch (e) {
    return undefined;
  if (url.origin === safeBaseUrl.origin) {
    return url.toString();
  return undefined;
[
  "javascript:alert(1)",
  "java%0d%0ascript%0d%0a:alert(0)",
  "javascript://%250Aalert(1)",
  "javascript://%250Aalert(1)//?1",
  "javascript://%250A1?alert(1):0",
  "%09Jav%09ascript:alert(document.domain)",
  "javascript://%250Alert(document.location=document.cookie)",
  "/%09/javascript:alert(1);",
  "/%09/javascript:alert(1)",
  "//%5cjavascript:alert(1);",
  "//%5cjavascript:alert(1)",
  "/%5cjavascript:alert(1);",
  "/%5cjavascript:alert(1)",
  "javascript://%0aalert(1)",
  "<>javascript:alert(1);",
  "//javascript:alert(1);",
  "//javascript:alert(1)",
  "/javascript:alert(1);",
  "/javascript:alert(1)",
  "javascript:alert(1)",
  "javascript:alert(1);",
  "javascript:alert(1)",
  "javascripT://anything%0D%0A%0D%0Awindow.alert(document.cookie)",
  "javascript:confirm(1)",
  "javascript://https://whitelisted.com/?z=%0Aalert(1)",
  "javascript:prompt(1)",
  "jaVAscript://whitelisted.com//%0d%0aalert(1);//",
  "javascript://whitelisted.com?%a0alert%281%29",
  "/x:1/:///%01javascript:alert(document.cookie)/",
  "<>//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "//;@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "/////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "/////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "///;@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "/.Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "/〱Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  ".Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "〱Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ%00｡Ｐⓦ",
  "%01https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "%01https://google.com",
  "////%09/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "///%09/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "//%09/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "/%09/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "////%09/google.com",
  "///%09/google.com",
  "//%09/google.com",
  "/%09/google.com",
  "////%09/whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "///%09/whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "//%09/whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "/%09/whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "////%09/whitelisted.com@google.com",
  "///%09/whitelisted.com@google.com",
  "//%09/whitelisted.com@google.com",
  "/%09/whitelisted.com@google.com",
  "&%0d%0a1Location:https://google.com",
  "%19Jav%09asc%09ript:https%20://whitelisted.com/%250Aconfirm%25281%2529",
  "////216.58.214.206",
  "///216.58.214.206",
  "//216.58.214.206",
  "/\\216.58.214.206",
  "/216.58.214.206",
  "216.58.214.206",
  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
  "%2f216.58.214.206//",
  "%2f216.58.214.206",
  "%2f216.58.214.206%2f%2f",
  "////Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
  "///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
  "/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
  "//%2f%2fⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "/%2f%2fⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "%2f$2f216.58.214.206",
  "$2f%2f216.58.214.206%2f%2f",
  "%2f$2f3627734734",
  "$2f%2f3627734734%2f%2f",
  "//%2f%2fgoogle.com",
  "/%2f%2fgoogle.com",
  "$2f%2fgoogle.com",
  "%2f$2fgoogle.com",
  "$2f%2fgoogle.com%2f%2f",
  "%2f3627734734//",
  "%2f3627734734",
  "%2f3627734734%2f%2f",
  "/%2f%5c%2f%67%6f%6f%67%6c%65%2e%63%6f%6d/",
  "/%2f%5c%2f%6c%6f%63%61%6c%64%6f%6d%61%69%6e%2e%70%77/",
  "%2fgoogle.com//",
  "%2fgoogle.com",
  "%2fgoogle.com%2f%2f",
  "////3627734734",
  "///3627734734",
  "//3627734734",
  "/3627734734",
  "3627734734",
  "//3H6k7lIAiqjfNeN@whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "//3H6k7lIAiqjfNeN@whitelisted.com+@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "//3H6k7lIAiqjfNeN@whitelisted.com@google.com/",
  "//3H6k7lIAiqjfNeN@whitelisted.com+@google.com/",
  "////%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "///%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "//%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "/%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "////%5cgoogle.com",
  "///%5cgoogle.com",
  "//%5cgoogle.com",
  "/%5cgoogle.com",
  "////%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "///%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "//%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "/%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "////%5cwhitelisted.com@google.com",
  "///%5cwhitelisted.com@google.com",
  "//%5cwhitelisted.com@google.com",
  "/%5cwhitelisted.com@google.com",
  "/%68%74%74%70%3a%2f%2f%67%6f%6f%67%6c%65%2e%63%6f%6d",
  "%68%74%74%70%3a%2f%2f%67%6f%6f%67%6c%65%2e%63%6f%6d",
  "%68%74%74%70%73%3a%2f%2f%6c%6f%63%61%6c%64%6f%6d%61%69%6e%2e%70%77",
  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ:80?@whitelisted.com/",
  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ:80#@whitelisted.com/",
  "\";alert(0);//",
  "data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=",
  "data:text/html;base64,PHNjcmlwdD5hbGVydCgiWFNTIik7PC9zY3JpcHQ+Cg==",
  "data:text/html;base64,PHNjcmlwdD5hbGVydCgiWFNTIik8L3NjcmlwdD4=",
  "data:whitelisted.com;text/html;charset=UTF-8,<html><script>document.write(document.domain);</script><iframe/src=xxxxx>aaaa</iframe></html>",
  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ%E3%80%82pw",
  "//google%00.com",
  "/google%252ecom",
  "google%252ecom",
  "<>//google.com",
  "/<>//google.com",
  "//;@google.com",
  "///;@google.com",
  "/////google.com/",
  "/////google.com",
  "////;@google.com",
  "////google.com//",
  "////google.com/",
  "////google.com",
  "///;@google.com",
  "///google.com//",
  "///google.com/",
  "///google.com",
  "///google.com/",
  "//google.com",
  "//google.com//",
  "//google.com/",
  "//google.com",
  "/.google.com",
  "///google.com/",
  "//google.com/",
  "//google.com",
  "/google.com",
  "/〱google.com",
  "/google.com",
  "../google.com",
  ".google.com",
  "@google.com",
  "//google.com/",
  "〱google.com",
  "google.com",
  "google.com%23@whitelisted.com",
  "////google.com/%2e%2e",
  "///google.com/%2e%2e",
  "//google.com/%2e%2e",
  "/google.com/%2e%2e",
  "//google.com/%2E%2E",
  "////google.com/%2e%2e%2f",
  "///google.com/%2e%2e%2f",
  "//google.com/%2e%2e%2f",
  "////google.com/%2f..",
  "///google.com/%2f..",
  "//google.com/%2f..",
  "//google.com/%2F..",
  "/google.com/%2F..",
  "////google.com/%2f%2e%2e",
  "///google.com/%2f%2e%2e",
  "//google.com/%2f%2e%2e",
  "/google.com/%2f%2e%2e",
  "//google.com//%2F%2E%2E",
  "//google.com:80?@whitelisted.com/",
  "//google.com:80#@whitelisted.com/",
  "google.com/.jpg",
  "//google.com\twhitelisted.com/",
  "//google.com/whitelisted.com",
  "//google.com@whitelisted.com",
  "google.com/whitelisted.com",
  "//google%E3%80%82com",
  "/http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "/http:/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "http://;@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "http://.Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "http:/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "http:Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "http://00330.00072.0000326.00000316",
  "http:00330.00072.0000326.00000316",
  "http://00330.0x3a.54990",
  "http:00330.0x3a.54990",
  "http://00330.3856078",
  "http:00330.3856078",
  "http://0330.072.0326.0316",
  "http:0330.072.0326.0316",
  "http:%0a%0dⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "http:%0a%0dgoogle.com",
  "http://0xd8.072.54990",
  "http:0xd8.072.54990",
  "http://0xd8.0x3a.0xd6.0xce",
  "http:0xd8.0x3a.0xd6.0xce",
  "http://0xd8.3856078",
  "http:0xd8.3856078",
  "http://0xd83ad6ce",
  "http:0xd83ad6ce",
  "http://[::216.58.214.206]",
  "http:[::216.58.214.206]",
  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ%23.whitelisted.com/",
  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ%2f%2f.whitelisted.com/",
  "http://3627734734",
  "http:3627734734",
  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ%3F.whitelisted.com/",
  "http://3H6k7lIAiqjfNeN@00330.00072.0000326.00000316",
  "http:3H6k7lIAiqjfNeN@00330.00072.0000326.00000316",
  "http://3H6k7lIAiqjfNeN@00330.0x3a.54990",
  "http:3H6k7lIAiqjfNeN@00330.0x3a.54990",
  "http://3H6k7lIAiqjfNeN@00330.3856078",
  "http:3H6k7lIAiqjfNeN@00330.3856078",
  "http://3H6k7lIAiqjfNeN@0330.072.0326.0316",
  "http:3H6k7lIAiqjfNeN@0330.072.0326.0316",
  "http://3H6k7lIAiqjfNeN@0xd8.072.54990",
  "http:3H6k7lIAiqjfNeN@0xd8.072.54990",
  "http://3H6k7lIAiqjfNeN@0xd8.0x3a.0xd6.0xce",
  "http:3H6k7lIAiqjfNeN@0xd8.0x3a.0xd6.0xce",
  "http://3H6k7lIAiqjfNeN@0xd8.3856078",
  "http:3H6k7lIAiqjfNeN@0xd8.3856078",
  "http://3H6k7lIAiqjfNeN@0xd83ad6ce",
  "http:3H6k7lIAiqjfNeN@0xd83ad6ce",
  "http://3H6k7lIAiqjfNeN@[::216.58.214.206]",
  "http:3H6k7lIAiqjfNeN@[::216.58.214.206]",
  "http://3H6k7lIAiqjfNeN@3627734734",
  "http:3H6k7lIAiqjfNeN@3627734734",
  "http://3H6k7lIAiqjfNeN@472.314.470.462",
  "http:3H6k7lIAiqjfNeN@472.314.470.462",
  "http://3H6k7lIAiqjfNeN@[::ffff:216.58.214.206]",
  "http:3H6k7lIAiqjfNeN@[::ffff:216.58.214.206]",
  "http://3H6k7lIAiqjfNeN@whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "http://3H6k7lIAiqjfNeN@whitelisted.com+@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "http://3H6k7lIAiqjfNeN@whitelisted.com@google.com/",
  "http://3H6k7lIAiqjfNeN@whitelisted.com+@google.com/",
  "http://472.314.470.462",
  "http:472.314.470.462",
  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ%5c%5c.whitelisted.com/",
  "/http://%67%6f%6f%67%6c%65%2e%63%6f%6d",
  "http://%67%6f%6f%67%6c%65%2e%63%6f%6d",
  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ:80?@whitelisted.com/",
  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ:80#@whitelisted.com/",
  "http://[::ffff:216.58.214.206]",
  "http:[::ffff:216.58.214.206]",
  "/http://google.com",
  "/http:/google.com",
  "http://;@google.com",
  "http://.google.com",
  "http://google.com",
  "http://google.com",
  "http:/google.com",
  "http:google.com",
  "http://google.com%23.whitelisted.com/",
  "http://google.com%2f%2f.whitelisted.com/",
  "http://google.com%3F.whitelisted.com/",
  "http://google.com%5c%5c.whitelisted.com/",
  "http://google.com:80?@whitelisted.com/",
  "http://google.com:80#@whitelisted.com/",
  "http://google.com\twhitelisted.com/",
  "//https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
  "/https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "https:Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "https://%09/Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "/https://%09/google.com",
  "https://%09/google.com",
  "https://%09/whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "https://%09/whitelisted.com@google.com",
  "https://%0a%0dⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "https://%0a%0dgoogle.com",
  "//https:///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
  "/https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
  "https:///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
  "//https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
  "/https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
  "/https:///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
  "/https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
  "https:///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
  "https%3a%2f%2fgoogle.com%2f",
  "/https://%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "/https:/%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "https://%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "https:/%5cⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "/https://%5cgoogle.com",
  "/https:/%5cgoogle.com/",
  "https://%5cgoogle.com",
  "https:/%5cgoogle.com/",
  "/https://%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "https://%5cwhitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "/https://%5cwhitelisted.com@google.com",
  "https://%5cwhitelisted.com@google.com",
  "https://%6c%6f%63%61%6c%64%6f%6d%61%69%6e%2e%70%77",
  "//https://google.com//",
  "/https://google.com//",
  "/https://google.com/",
  "/https://google.com",
  "/https:google.com",
  "https://////google.com",
  "https://google.com//",
  "https://google.com/",
  "https://google.com",
  "https:/google.com",
  "https:google.com",
  "//https:///google.com/%2e%2e",
  "/https://google.com/%2e%2e",
  "https:///google.com/%2e%2e",
  "//https://google.com/%2e%2e%2f",
  "https://google.com/%2e%2e%2f",
  "/https://google.com/%2f..",
  "https://google.com/%2f..",
  "/https:///google.com/%2f%2e%2e",
  "/https://google.com/%2f%2e%2e",
  "https:///google.com/%2f%2e%2e",
  "https://google.com/%2f%2e%2e",
  "https://:@google.com@whitelisted.com",
  "https://google.com?whitelisted.com",
  "https://google.com/whitelisted.com",
  "https://google.comwhitelisted.com",
  "https://google.com#whitelisted.com",
  "https://google%E3%80%82com",
  "//https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
  "/https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "https://:@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ@whitelisted.com",
  "https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/whitelisted.com",
  "https://whitelisted.com;@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "/https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
  "https:///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
  "//https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
  "/https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
  "/https:///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
  "/https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
  "https:///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
  "https://whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
  "//https://whitelisted.com@google.com//",
  "/https://whitelisted.com@google.com/",
  "https://whitelisted.com;@google.com",
  "https://whitelisted.com.google.com",
  "https://whitelisted.com@google.com//",
  "https://whitelisted.com@google.com/",
  "https://whitelisted.com@google.com",
  "/https://whitelisted.com@google.com/%2e%2e",
  "https:///whitelisted.com@google.com/%2e%2e",
  "//https://whitelisted.com@google.com/%2e%2e%2f",
  "https://whitelisted.com@google.com/%2e%2e%2f",
  "/https://whitelisted.com@google.com/%2f..",
  "https://whitelisted.com@google.com/%2f..",
  "/https:///whitelisted.com@google.com/%2f%2e%2e",
  "/https://whitelisted.com@google.com/%2f%2e%2e",
  "https:///whitelisted.com@google.com/%2f%2e%2e",
  "https://whitelisted.com@google.com/%2f%2e%2e",
  "/https://whitelisted.com@google.com/%2f.//whitelisted.com@google.com/%2f..",
  "https://whitelisted.com/https://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "https://whitelisted.com/https://google.com/",
  "@https://www.google.com",
  "http://Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ\twhitelisted.com/",
  "http://whitelisted.com@00330.00072.0000326.00000316",
  "http:whitelisted.com@00330.00072.0000326.00000316",
  "http://whitelisted.com@00330.0x3a.54990",
  "http:whitelisted.com@00330.0x3a.54990",
  "http://whitelisted.com@00330.3856078",
  "http:whitelisted.com@00330.3856078",
  "http://whitelisted.com@0330.072.0326.0316",
  "http:whitelisted.com@0330.072.0326.0316",
  "http://whitelisted.com@0xd8.072.54990",
  "http:whitelisted.com@0xd8.072.54990",
  "http://whitelisted.com@0xd8.0x3a.0xd6.0xce",
  "http:whitelisted.com@0xd8.0x3a.0xd6.0xce",
  "http://whitelisted.com@0xd8.3856078",
  "http:whitelisted.com@0xd8.3856078",
  "http://whitelisted.com@0xd83ad6ce",
  "http:whitelisted.com@0xd83ad6ce",
  "http://whitelisted.com@[::216.58.214.206]",
  "http:whitelisted.com@[::216.58.214.206]",
  "http://whitelisted.com%2eⓁ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "http://whitelisted.com%2egoogle.com/",
  "http://whitelisted.com@3627734734",
  "http:whitelisted.com@3627734734",
  "http://whitelisted.com@472.314.470.462",
  "http:whitelisted.com@472.314.470.462",
  "http://whitelisted.com:80%40Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "http://whitelisted.com:80%40google.com/",
  "http://whitelisted.com@[::ffff:216.58.214.206]",
  "http:whitelisted.com@[::ffff:216.58.214.206]",
  "http://whitelisted.com@google.com/",
  "http://whitelisted.com+&@google.com#+@whitelisted.com/",
  "http://whitelisted.com+&@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ#+@whitelisted.com/",
  "http://www.google.com.whitelisted.com",
  "http://www.Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ.whitelisted.com",
  "ja\nva\tscript\r:alert(1)",
  "java%09script:alert(1)",
  "java%0ascript:alert(1)",
  "java%0d%0ascript%0d%0a:alert(0)",
  "java%0dscript:alert(1)",
  "Javas%26%2399;ript:alert(1)",
  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ\twhitelisted.com/",
  "\u006A\u0061\u0076\u0061\u0073\u0063\u0072\u0069\u0070\u0074\u003aalert(1)",
  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/whitelisted.com",
  "//Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ@whitelisted.com",
  "//whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ//",
  "//whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/",
  "Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/whitelisted.com",
  "whitelisted.com;@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ",
  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
  "//whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e%2f",
  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
  "//whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f..",
  "////whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
  "///whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
  "//whitelisted.com@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2f%2e%2e",
  "/whitelisted.com:80%40google.com",
  "whitelisted.com@%E2%80%AE@google.com",
  "////whitelisted.com@google.com//",
  "////whitelisted.com@google.com/",
  "///whitelisted.com@google.com//",
  "///whitelisted.com@google.com/",
  "//whitelisted.com@google.com//",
  "//whitelisted.com@google.com/",
  "whitelisted.com;@google.com",
  "whitelisted.com.google.com",
  "////whitelisted.com@google.com/%2e%2e",
  "///whitelisted.com@google.com/%2e%2e",
  "////whitelisted.com@google.com/%2e%2e%2f",
  "///whitelisted.com@google.com/%2e%2e%2f",
  "//whitelisted.com@google.com/%2e%2e%2f",
  "////whitelisted.com@google.com/%2f..",
  "///whitelisted.com@google.com/%2f..",
  "//whitelisted.com@google.com/%2f..",
  "////whitelisted.com@google.com/%2f%2e%2e",
  "///whitelisted.com@google.com/%2f%2e%2e",
  "//whitelisted.com@google.com/%2f%2e%2e",
  "//whitelisted.com+&@google.com#+@whitelisted.com/",
  "//whitelisted.com@https:///Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ/%2e%2e",
  "//whitelisted.com@https:///google.com/%2e%2e",
  "//whitelisted.com+&@Ⓛ𝐨𝗰�𝕝ⅆ𝓸ⓜₐℹⓃ｡Ｐⓦ#+@whitelisted.com/"
]
      returnTo: new URL('/foo', withoutApi.baseURL).toString()
      returnTo: new URL('/foo', withoutApi.baseURL).toString()
    const cookieJar = new CookieJar();
    await get(baseUrl, '/api/auth/login?returnTo=https://www.google.com', { cookieJar });
    const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie;

    const decodedState = decodeState(state.split('.')[0]);
    expect(decodedState).toEqual({});
  });

  test('should allow absolute urls in params of returnTo urls', async () => {
    const loginOptions = {
      returnTo: '/default-redirect'
    };
    const baseUrl = await setup(withoutApi, { loginOptions });

    const cookieJar = new CookieJar();
    await get(baseUrl, '/api/auth/login?returnTo=/foo?url=https://www.google.com', { cookieJar });
    const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie;

    const decodedState = decodeState(state.split('.')[0]);
    expect(decodedState).toEqual({
      returnTo: new URL('/foo?url=https://www.google.com', withoutApi.baseURL).toString()
    });
  });

  test('should redirect relative to the redirect_uri over the base url', async () => {
    const loginOptions = {
      returnTo: '/default-redirect',
      authorizationParams: {
        redirect_uri: 'https://other-org.acme.com/api/auth/callback'
      }
    };
    const baseUrl = await setup(withoutApi, { loginOptions });

    const cookieJar = new CookieJar();
    await get(baseUrl, '/api/auth/login?returnTo=/foo', { cookieJar });
    const { value: state } = getCookie('state', cookieJar, baseUrl) as Cookie;

    const decodedState = decodeState(state.split('.')[0]);
    expect(decodedState).toEqual({
      returnTo: 'https://other-org.acme.com/foo'
    });
import toSafeRedirect from '../../src/utils/url-helpers';
import payloads from '../fixtures/open-redirect-payloads.json';
  const safeBaseUrl = new URL('http://www.example.com');

      expect(toSafeRedirect('file://foo', safeBaseUrl)).toEqual(undefined);
      expect(toSafeRedirect('https://foo', safeBaseUrl)).toEqual(undefined);
      expect(toSafeRedirect('http://foo', safeBaseUrl)).toEqual(undefined);
      expect(toSafeRedirect('/foo', safeBaseUrl)).toEqual('http://www.example.com/foo');
      expect(toSafeRedirect('foo', safeBaseUrl)).toEqual('http://www.example.com/foo');
      expect(toSafeRedirect('/foo?some=value', safeBaseUrl)).toEqual('http://www.example.com/foo?some=value');
      expect(toSafeRedirect('/foo?someUrl=https://www.google.com', safeBaseUrl)).toEqual(
        'http://www.example.com/foo?someUrl=https://www.google.com'
      );
      expect(toSafeRedirect('/foo', new URL('http://www.example.com:8888'))).toEqual('http://www.example.com:8888/foo');
      for (const payload of payloads) {
        expect(toSafeRedirect(payload, safeBaseUrl) || safeBaseUrl.toString()).toMatch(/^http:\/\/www.example.com\//);
      }
    test('should not throw for empty redirect', () => {
      expect(toSafeRedirect.bind(null, '', safeBaseUrl)).not.toThrow();