Repository: auth0/nextjs-auth0 (ts)

Issue:
When somewhere in the returnTo Url a absolute url found -> Exeption
### Description

On our Project we have the following issue.

Our returnTo Url looks for example like this -> .../login?returnTo=/relative/return/page/with/params?someUrl=https://foo.bar

Because of the way how relative URL tested (Check of // somewhere in string) the lib says that no relative url is given

Can u get a look on this? ... I would make a PR to fix this

Thanks

Pull request description:
Enforce configured host on user supplied returnTo
### Description

This PR changes how the user supplied `returnTo` param from `/api/auth/login` is sanitized.

Instead of using a regexp like a block list to prevent URLs that aren't relative from being accepted, it converts the URL to absolute using the configured `redirect_uri` as a base. It then enforces that the absolute URL has the same origin as the configured `redirect_uri`. 

This will ensure that only valid URLs with the same origin as the configured `redirect_uri` (or `baseURL`) are accepted as `returnTo` URLs.

### References

Fixes #545 
See also #555 

### Testing

Tested using https://github.com/adamjmcgrath/open-redirect-finder


Patch:
diff --git a/src/handlers/login.ts b/src/handlers/login.ts
index ed24eeb0..21e02a8f 100644
--- a/src/handlers/login.ts
+++ b/src/handlers/login.ts
@@ -1,8 +1,8 @@
 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';
 
 /**
@@ -117,16 +117,19 @@ export type HandleLogin = (req: NextApiRequest, res: NextApiResponse, options?:
 /**
  * @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 };
       }
diff --git a/src/index.ts b/src/index.ts
index baf7731e..22e4405f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -77,7 +77,7 @@ export const initAuth0: InitAuth0 = (params) => {
   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);
diff --git a/src/utils/url-helpers.ts b/src/utils/url-helpers.ts
index ac0fbcf6..32e5b4b0 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;
 }


Test patch:
diff --git a/tests/fixtures/open-redirect-payloads.json b/tests/fixtures/open-redirect-payloads.json
new file mode 100644
index 00000000..1bb14216
--- /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 88bc9e1d..bf9c97a5 100644
--- a/tests/handlers/login.test.ts
+++ b/tests/handlers/login.test.ts
@@ -214,7 +214,7 @@ describe('login handler', () => {
 
     const decodedState = decodeState(state.split('.')[0]);
     expect(decodedState).toEqual({
-      returnTo: '/foo'
+      returnTo: new URL('/foo', withoutApi.baseURL).toString()
     });
   });
 
@@ -229,7 +229,7 @@ describe('login handler', () => {
 
     const decodedState = decodeState(state.split('.')[0]);
     expect(decodedState).toEqual({
-      returnTo: '/foo'
+      returnTo: new URL('/foo', withoutApi.baseURL).toString()
     });
   });
 
@@ -239,9 +239,47 @@ describe('login handler', () => {
     };
     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 () => {
diff --git a/tests/utils/url-helpers.test.ts b/tests/utils/url-helpers.test.ts
index 50b7a8c5..0cf719bd 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();
     });
   });
 });