Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

feat(nuxt): navigateTo supports external redirects #5022

Merged
merged 17 commits into from
Aug 24, 2022

Conversation

manniL
Copy link
Member

@manniL manniL commented May 17, 2022

πŸ”— Linked issue

https://github.com/nuxt/framework/discussions/4997, resolves nuxt/nuxt#14067

❓ Type of change

  • πŸ“– Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • πŸ‘Œ Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

πŸ“š Description

Right now, navigateTo supports only internal redirects (either via vue-router or by using the baseURL).
This PR adds rudimentary support for external redirects and protocol checking:

  1. It checks if the link passed in is external (if it has a protocol based on ufo)
  2. If the protocol is script:, Nuxt will immediately throw an error on client- and server-side
  3. If allowExternal is not set to true, then a confirmation dialog will pop up on client-side and an error will be thrown on server-side
  4. If it is set to true it'll use h3 on the server-side or location.replace/location.href = X on the client-side

Also feel free to tell me if that's the right approach or if we want another function for this behavior πŸ€”
Doc update will follow when the PR is more mature.

πŸ“ Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@netlify
Copy link

netlify bot commented May 17, 2022

βœ… Deploy Preview for nuxt3-docs canceled.

Name Link
πŸ”¨ Latest commit d97e68a
πŸ” Latest deploy log https://app.netlify.com/sites/nuxt3-docs/deploys/630647e659153b0007be89a0

@atinux atinux requested a review from danielroe May 17, 2022 10:18
@UnderKoen
Copy link

Should it only allow http/https based urls? Or would mailto and tel also need to work?

A check that does include mailto and tel.

try {
	url = new URL(string);
	//url route
} catch (_) {
	//normal route
}

Maybe it would be even better to have no check and have a additional entry in the to object so that the following would work:

navigateTo({url: "https://github.com"});

@manniL
Copy link
Member Author

manniL commented May 17, 2022

Should it only allow http/https based urls? Or would mailto and tel also need to work?

A check that does include mailto and tel.

try {
	url = new URL(string);
	//url route
} catch (_) {
	//normal route
}

Great idea! Will incorporate that in the code ☺️

Maybe it would be even better to have no check and have a additional entry in the to object so that the following would work:

navigateTo({url: "https://github.com"});

That would be one option but would leave the "check" up to the user. IMO the DX is better when Nuxt is doing the check for the user instead of leaving it up to them.

packages/nuxt/src/app/composables/router.ts Outdated Show resolved Hide resolved
packages/nuxt/src/app/composables/router.ts Outdated Show resolved Hide resolved
packages/nuxt/src/app/composables/router.ts Outdated Show resolved Hide resolved
@pi0
Copy link
Member

pi0 commented May 20, 2022

Thanks for this PR @manniL πŸ’š In general this is a really nice improvement that we support redirection to external URLs but I'm also little bit concerned about potential security risks implied by combination of this new feature and developer's mistake with a programmatic call. I suggest some options:

  • Explicit protocol validation to avoid things like script:
  • A new option externalURL: '' / external: true and abort when url is external with an error to use explicit option for external URL. (We might also use a simple alert/prompt fallback to concenst users about redireting when url is implicitly external)

@manniL
Copy link
Member Author

manniL commented May 20, 2022

Thanks for this PR @manniL πŸ’š In general this is a really nice improvement that we support redirection to external URLs but I'm also little bit concerned about potential security risks implied by combination of this new feature and developer's mistake with a programmatic call. I suggest some options:

* Explicit protocol validation to avoid things like `script:`

* A new option `externalURL: ''` / `external: true` and abort when `url` is external with an error to use explicit option for external URL. (We might also use a simple alert/prompt fallback to concenst users about redireting when `url` is implicitly external)

Thanks for the feedback! πŸ™
I agree regarding some security implications here. DX-wise, the protocol validation part would be more appealing IMO but from a security POV, a new option would make more sense.

What would be your favorite solution here?

@pi0
Copy link
Member

pi0 commented May 20, 2022

I would probably do both:

  • Protocol restriction in all cases
  • Abort SSR / Prompt Client side with url: 'https://...'
  • Introduce external: true or externalURL: string (Which one you think is better?)

@manniL
Copy link
Member Author

manniL commented May 20, 2022

* Protocol restriction in all cases

That means not executing any links that start with script:. Any other protocols we should consider?

* Abort SSR / Prompt Client side with `url: 'https://...'`

By throwing an error with the approriate message, right?

* Introduce `external: true` or `externalURL: string` (Which one you think is better?)

I'd probably go with external: true. This param won't have any influence when the URL is local and will only matter in case the path is actually an external one, right?

@pi0
Copy link
Member

pi0 commented May 31, 2022

That means not executing any links that start with script:. Any other protocols we should consider?

Considering the possibility of native app linking with custom protocols, yes the best we can exclude unsafe script: protocol.

By throwing an error with the appropriate message, right?

Would be an overkill to use good-old window.confirm as the default fallback? For server yes error is the best!

I'd probably go with external: true. This param won't have any influence when the URL is local and will only matter in case the path is actually an external one, right?

external: true seems good to me too! We can document to use external only if you are sure external URLs are safe. Or what about allowExternal?

@cawa-93
Copy link
Contributor

cawa-93 commented Jun 2, 2022

I will add a little feedback as developer who faced with this problem. In general, reading the documentation, one gets the impression that the <nuxt-link>and navigateTo work identically. We can easily pass the external url in to without any additional: <nuxt-link to="https://github.com">. And I would expect the navigateTo to take exactly the same parameters and process them identically: navigateTo('https://github.com')

@manniL manniL marked this pull request as draft June 17, 2022 08:07
@manniL manniL marked this pull request as ready for review June 17, 2022 08:49
@manniL manniL requested review from pi0 and danielroe June 17, 2022 08:49
@manniL
Copy link
Member Author

manniL commented Jun 17, 2022

Updated the PR with the recommended changes πŸ˜‹

@justin-schroeder
Copy link
Sponsor

Until this is merged β€”Β I've worked up a small composable function to allow explicit external redirects from route middleware: https://gist.github.com/justin-schroeder/f583797c702155f32c9078d1add2a594

@serhez
Copy link

serhez commented Jul 21, 2022

Hello! Are there any updates on this? It is quite critical for important workflows like OAuth 2.0 (being able to follow a server redirection to the login form, etc.), unless there is some other way to do it I missed.

@OhB00
Copy link
Contributor

OhB00 commented Aug 9, 2022

Found an interesting workaround for this one, prepending your URL with // enables you to perform an external redirect. For example, to redirect to http://google.com use navigateTo("//google.com"). This works provided your baseUrl is / as ufo will strip a slash from the start of your path.

Works because of parser differentials between the browser and vue-router/ufo.

Couldn't find a way to do this for other protocols.

@manniL
Copy link
Member Author

manniL commented Aug 24, 2022

@pi0 Please let me know what is left to do here besides the docs PR ☺️

@pi0
Copy link
Member

pi0 commented Aug 24, 2022

Thanks again for PR @manniL and sorry it took long. Finally, I've made few refactors to inline and simplify logic, avoid extra parse calls, improve messages and make the option simpler { external: true }. I hope this changes are fine to you.

} else {
location.href = toPath
}
return Promise.resolve()
Copy link
Member

@pi0 pi0 Aug 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last thing to ensure: In nuxt 2, we had this problem with external navigations that during navigation, rest of logic goes on. And solution was a custom made error because it was sync. I think we could do something like a never resolving promise that avoids this. (will make a reproduction and implement in later PR btw need more testing) => https://github.com/nuxt/framework/issues/6906

@pi0 pi0 merged commit a4dfe23 into nuxt:main Aug 24, 2022
@pi0
Copy link
Member

pi0 commented Aug 24, 2022

(We also need to document new external key πŸ™ˆ ) => nuxt/nuxt#14704

@manniL manniL deleted the feat-redirect-external branch August 25, 2022 09:30
@pi0 pi0 mentioned this pull request Aug 26, 2022
if (process.client && isProcessingMiddleware()) {

const toPath = typeof to === 'string' ? to : ((to as RouteLocationPathRaw).path || '/')
const isExternal = hasProtocol(toPath, true)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why you check protocol for external? may be I want to redirect within current domain, i.e. respond with headers

HTTP/1.1 302 Found
Location: /my-legacy-page

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you please open an issue with a sandbox to track? πŸ™πŸΌ

@danielroe danielroe added the 3.x label Jan 19, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Imposible redirect to an external address in page middleware
9 participants