Protect your Magic Links from email clients
5 March 2024When 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:
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 allow for redirection to the authentication service endpoint seamlessly.
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 and then redirects the user to the auth service. The following is an example of such page in React.
_33import { useEffect, useState } from 'react'_33import { useNhostClient } from '@nhost/react'_33import { useSearchParams } from 'react-router-dom'_33_33const VerifyPage: React.FC = () => {_33 const nhost = useNhostClient()_33 const [loading, setLoading] = useState(true)_33 const [searchParams] = useSearchParams()_33_33 useEffect(() => {_33 const ticket = searchParams.get('ticket')_33 const redirectTo = searchParams.get('redirectTo')_33 const type = searchParams.get('type')_33_33 if (ticket && redirectTo && type) {_33 window.location.href = `${nhost.auth.url}/verify?ticket=${ticket}&type=${type}&redirectTo=${redirectTo}`_33 }_33_33 setLoading(false)_33 }, [searchParams, nhost?.auth?.url])_33_33 if (loading) {_33 return null_33 }_33_33 return (_33 <div>_33 <span>Failed to authenticate with magick link</span>_33 </div>_33 )_33}_33_33export default VerifyPage
Now here's the sequence diagram after applying the above strategy:
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.