Back to Blog
Product | Auth | Email | Magic Link

Protect your Magic Links from email clients

5 March 2024
Transparent lines
Banner of Protect your Magic Links from email clients

When working on a project involving user authentication, it's crucial to incorporate email verification and password reset capabilities. Additionally, Magic links provide a seamless mechanism by allowing users to log in without a password altogether. All of these methods share a common feature: they rely on sending a link to the user via email.

In recent years, email clients have undergone significant evolution, introducing numerous features and security enhancements. However, these advancements can sometimes disrupt the integrity of embedded links, potentially resulting in unintended consequences. Notable among these features are the ability to crawl links within emails to generate inline previews and automated security scans that seek out malicious content.

Since Magic Links are designed for one-time use, one of those features can occasionally render the links invalid. Consequently, your users would encounter a broken link, disrupting their intended access.

Here's a sequence diagram to better explain what happens in the case of a reset password flow:

Reset password flow without redirect

To address this issue, you need to implement an additional step by refraining from sending the magic link directly in the email. Instead, you can include a link to a specific page within your app that contains the required parameters in the URL. This setup would ensure that only a real user can trigger the redirection to the authentication service endpoint.

When performing reset the password flow, users typically receive an email containing a link structured as follows:
https://[subdomain].auth.[region].nhost.run/v1/verify?ticket=...&type=...&redirectTo=...

Instead of sending this link, you redirect users to your app with the all the required parameters. Fortunately we make it super easy to do this. You only have to edit the relevant email template in your nhost folder under nhost/emails/en/reset-password/body.html.


_19
<!DOCTYPE html>
_19
<html>
_19
_19
<head>
_19
<meta charset="utf-8" />
_19
</head>
_19
_19
<body>
_19
<h2>Reset Password</h2>
_19
<p>Use this link to reset your password:</p>
_19
<p>
_19
- <a href="${link}">
_19
+ <a href="${clientUrl}/verify?ticket=${ticket}&redirectTo=${redirectTo}&type=emailVerify">
_19
Reset password
_19
</a>
_19
</p>
_19
</body>
_19
_19
</html>

Then, in your app, you add another page that reads those parameters from the URL. This new page will include a button that users can click on to trigger the verification process and redirect them to the auth service. The following is an example of such a page in React.


_28
import { useNhostClient } from '@nhost/react'
_28
import { useSearchParams } from 'react-router-dom'
_28
_28
const VerifyPage = () => {
_28
const nhost = useNhostClient()
_28
const [searchParams] = useSearchParams()
_28
_28
const redirectToVerificationLink = () => {
_28
const ticket = searchParams.get('ticket')
_28
const type = searchParams.get('type')
_28
const redirectTo = searchParams.get('redirectTo')
_28
_28
if (ticket && type && redirectTo) {
_28
window.location.href = `${nhost.auth.url}/verify?ticket=${ticket}&type=${type}&redirectTo=${redirectTo}`
_28
}
_28
}
_28
_28
return (
_28
<div>
_28
<span>Please verify your account by clicking the link below.</span>
_28
<button onClick={redirectToVerificationLink}>
_28
Verify
_28
</Button>
_28
</div>
_28
)
_28
}
_28
_28
export default VerifyPage

Now here's the sequence diagram after applying the above strategy:

Reset password flow with redirect

Wrap up

The strategy detailed in this blog post isn't limited to password reset flows but can also be applied in other use cases such as email verification and one-time file download links. By centralizing link handling within the application, the validity of such links remains intact across various email clients and scenarios.

Share this post

Twitter LogoLinkedIn LogoFacebook Logo