Git Product home page Git Product logo

remix-auth-email-link's Introduction

Email Link Strategy - Remix Auth

This strategy is heavily based on kcd strategy present in the v2 of Remix Auth. The major difference being we are using crypto-js instead of crypto so that it can be deployed on CF.

The Email Link Strategy implements the authentication strategy used on kentcdodds.com.

This strategy uses passwordless flow with magic links. A magic link is a special URL generated when the user tries to login, this URL is sent to the user via email, after the click on it the user is automatically logged in.

You can read more about how this work in the kentcdodds.com/how-i-built-a-modern-website-in-2021.

Supported runtimes

Runtime Has Support
Node.js โœ…
Cloudflare โœ…

How to use

Setup

Because of how this strategy works you need a little bit more setup than other strategies, but nothing specially crazy.

Email Service

You will need to have some email service configured in your application. What you actually use to send emails is not important, as far as you can create a function with this type:

type SendEmailOptions<User> = {
  emailAddress: string
  magicLink: string
  user?: User | null
  domainUrl: string
  form: FormData
}

type SendEmailFunction<User> = {
  (options: SendEmailOptions<User>): Promise<void>
}

So if you have something like app/services/email-provider.server.ts file exposing a generic function like sendEmail function receiving an email address, subject and body, you could use it like this:

// app/services/email.server.tsx
import { renderToString } from 'react-dom/server'
import type { SendEmailFunction } from 'remix-auth-email-link'
import type { User } from '~/models/user.model'
import * as emailProvider from '~/services/email-provider.server'

export let sendEmail: SendEmailFunction<User> = async (options) => {
  let subject = "Here's your Magic sign-in link"
  let body = renderToString(
    <p>
      Hi {options.user?.name || 'there'},<br />
      <br />
      <a href={options.magicLink}>Click here to login on example.app</a>
    </p>
  )

  await emailProvider.sendEmail(options.emailAddress, subject, body)
}

Again, what you use as email provider is not important, you could use a third party service like Mailgun or Sendgrid, if you are using AWS you could use SES.

Create the strategy instance

Now that you have your sendEmail email function you can create an instance of the Authenticator and the EmailLinkStrategy.

// app/services/auth.server.ts
import { Authenticator } from 'remix-auth'
import { EmailLinkStrategy } from 'remix-auth-email-link'
import { sessionStorage } from '~/services/session.server'
import { sendEmail } from '~/services/email.server'
import { User, getUserByEmail } from '~/models/user.server'

// This secret is used to encrypt the token sent in the magic link and the
// session used to validate someone else is not trying to sign-in as another
// user.
let secret = process.env.MAGIC_LINK_SECRET
if (!secret) throw new Error('Missing MAGIC_LINK_SECRET env variable.')

export let auth = new Authenticator<User>(sessionStorage)

// Here we need the sendEmail, the secret and the URL where the user is sent
// after clicking on the magic link
auth.use(
  new EmailLinkStrategy(
    { sendEmail, secret, callbackURL: '/magic' },
    // In the verify callback,
    // you will receive the email address, form data and whether or not this is being called after clicking on magic link
    // and you should return the user instance
    async ({
      email,
      form,
      magicLinkVerify,
    }: {
      email: string
      form: FormData
      magicLinkVerify: boolean
    }) => {
      let user = await getUserByEmail(email)
      return user
    }
  )
)

Setup your routes

Now you can proceed to create your routes and do the setup.

// app/routes/login.tsx
import { ActionArgs, LoaderArgs } from '@remix-run/node'
import { json } from '@remix-run/node'
import { Form, useLoaderData } from '@remix-run/react'
import { auth } from '~/services/auth.server'
import { sessionStorage } from '~/services/session.server'

export let loader = async ({ request }: LoaderArgs) => {
  await auth.isAuthenticated(request, { successRedirect: '/me' })
  let session = await sessionStorage.getSession(request.headers.get('Cookie'))
  // This session key `auth:magiclink` is the default one used by the EmailLinkStrategy
  // you can customize it passing a `sessionMagicLinkKey` when creating an
  // instance.
  return json({
    magicLinkSent: session.has('auth:magiclink'),
    magicLinkEmail: session.get('auth:email'),
  })
}

export let action = async ({ request }: ActionArgs) => {
  // The success redirect is required in this action, this is where the user is
  // going to be redirected after the magic link is sent, note that here the
  // user is not yet authenticated, so you can't send it to a private page.
  await auth.authenticate('email-link', request, {
    successRedirect: '/login',
    // If this is not set, any error will be throw and the ErrorBoundary will be
    // rendered.
    failureRedirect: '/login',
  })
}

// app/routes/login.tsx
export default function Login() {
  let { magicLinkSent, magicLinkEmail } = useLoaderData<typeof loader>()

  return (
    <Form action="/login" method="post">
      {magicLinkSent ? (
        <p>
          Successfully sent magic link{' '}
          {magicLinkEmail ? `to ${magicLinkEmail}` : ''}
        </p>
      ) : (
        <>
          <h1>Log in to your account.</h1>
          <div>
            <label htmlFor="email">Email address</label>
            <input id="email" type="email" name="email" required />
          </div>
          <button>Email a login link</button>
        </>
      )}
    </Form>
  )
}
// app/routes/magic.tsx
import { LoaderArgs } from '@remix-run/node'
import { auth } from '~/services/auth.server'

export let loader = async ({ request }: LoaderArgs) => {
  await auth.authenticate('email-link', request, {
    // If the user was authenticated, we redirect them to their profile page
    // This redirect is optional, if not defined the user will be returned by
    // the `authenticate` function and you can render something on this page
    // manually redirect the user.
    successRedirect: '/me',
    // If something failed we take them back to the login page
    // This redirect is optional, if not defined any error will be throw and
    // the ErrorBoundary will be rendered.
    failureRedirect: '/login',
  })
}
// app/routes/me.tsx
import { LoaderArgs } from '@remix-run/node'
import { json } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'
import { auth } from '~/services/auth.server'

export let loader = async ({ request }: LoaderArgs) => {
  // If the user is here, it's already authenticated, if not redirect them to
  // the login page.
  let user = await auth.isAuthenticated(request, { failureRedirect: '/login' })
  return json({ user })
}

export default function Me() {
  let { user } = useLoaderData<typeof loader>()
  return (
    <div>
      <h1>Welcome {user.name}</h1>
      <p>You are logged in as {user.email}</p>
    </div>
  )
}

Email validation

The EmailLinkStrategy also supports email validation, this is useful if you want to prevent someone from signing-in with a disposable email address or you have some denylist of emails for some reason.

By default, the EmailStrategy will validate every email against the regular expression /.+@.+/, if it doesn't pass it will throw an error.

If you want to customize it you can create a function with this type and pass it to the EmailLinkStrategy.

type VerifyEmailFunction = {
  (email: string): Promise<void>
}

Example

// app/services/verifier.server.ts
import { VerifyEmailFunction } from 'remix-auth-email-link'
import { isEmailBurner } from 'burner-email-providers'
import isEmail from 'validator/lib/isEmail'

export let verifyEmailAddress: VerifyEmailFunction = async (email) => {
  if (!isEmail(email)) throw new Error('Invalid email address.')
  if (isEmailBurner(email)) throw new Error('Email not allowed.')
}
// app/services/auth.server.ts
import { Authenticator } from 'remix-auth'
import { Authenticator, EmailLinkStrategy } from 'remix-auth-email-link'
import { sessionStorage } from '~/services/session.server'
import { sendEmail } from '~/services/email.server'
import { User, getUserByEmail } from '~/models/user.model'
import { verifyEmailAddress } from '~/services/verifier.server'

// This secret is used to encrypt the token sent in the magic link and the
// session used to validate someone else is not trying to sign-in as another
// user.
let secret = process.env.MAGIC_LINK_SECRET
if (!secret) throw new Error('Missing MAGIC_LINK_SECRET env variable.')

let auth = new Authenticator<User>(sessionStorage)

// Here we need the sendEmail, the secret and the URL where the user is sent
// after clicking on the magic link
auth.use(
  new EmailLinkStrategy(
    { verifyEmailAddress, sendEmail, secret, callbackURL: '/magic' },
    // In the verify callback you will only receive the email address and you
    // should return the user instance
    async ({ email }: { email: string }) => {
      let user = await getUserByEmail(email)
      return user
    }
  )
)

Options options

The EmailLinkStrategy supports a few more optional configuration options you can set. Here's the whole type with each option commented.

type EmailLinkStrategyOptions<User> = {
  /**
   * The endpoint the user will go after clicking on the email link.
   * A whole URL is not required, the pathname is enough, the strategy will
   * detect the host of the request and use it to build the URL.
   * @default "/magic"
   */
  callbackURL?: string
  /**
   * A function to send the email. This function should receive the email
   * address of the user and the URL to redirect to and should return a Promise.
   * The value of the Promise will be ignored.
   */
  sendEmail: SendEmailFunction<User>
  /**
   * A function to validate the email address. This function should receive the
   * email address as a string and return a Promise. The value of the Promise
   * will be ignored, in case of error throw an error.
   *
   * By default it only test the email against the RegExp `/.+@.+/`.
   */
  verifyEmailAddress?: VerifyEmailFunction
  /**
   * A secret string used to encrypt and decrypt the token and magic link.
   */
  secret: string
  /**
   * The name of the form input used to get the email.
   * @default "email"
   */
  emailField?: string
  /**
   * The param name the strategy will use to read the token from the email link.
   * @default "token"
   */
  magicLinkSearchParam?: string
  /**
   * How long the magic link will be valid. Default to 30 minutes.
   * @default 1_800_000
   */
  linkExpirationTime?: number
  /**
   * The key on the session to store any error message.
   * @default "auth:error"
   */
  sessionErrorKey?: string
  /**
   * The key on the session to store the magic link.
   * @default "auth:magiclink"
   */
  sessionMagicLinkKey?: string
  /**
   * Add an extra layer of protection and validate the magic link is valid.
   * @default false
   */
  validateSessionMagicLink?: boolean

  /**
   * The key on the session to store the email.
   * It's unset the same time the sessionMagicLinkKey is.
   * @default "auth:email"
   */
  sessionEmailKey?: string
}

remix-auth-email-link's People

Contributors

dependabot[bot] avatar edgesoft avatar ericallam avatar macklinu avatar manosim avatar michaeldeboey avatar pbteja1998 avatar wild-lotus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

remix-auth-email-link's Issues

Magic link should be sent without requiring a redirect

Describe the bug

Right now, if I do not include a successRedirect upon sending of the magic link, the code will throw an error, e.g.

// Libraries
import { json } from "@remix-run/node";
import { auth } from "~/services/auth.server";
import { sessionStorage } from "~/services/session.server";

export let loader = async ({ request }) => {
  await auth.isAuthenticated(request, { successRedirect: "/profile" });
  let session = await sessionStorage.getSession(request.headers.get("Cookie"));
  return json({
    magicLinkSent: session.has("auth:magiclink"),
    magicLinkEmail: session.get("auth:email"),
  });
};

export let action = async ({ request }) => {
  await auth.authenticate("email-link", request, {
    // successRedirect: "/",
  });
};

Your Example Website or App

This is a local site, but the code can be seen above.

Steps to Reproduce the Bug or Issue

// Libraries
import { json } from "@remix-run/node";
import { auth } from "~/services/auth.server";
import { sessionStorage } from "~/services/session.server";

export let loader = async ({ request }) => {
  await auth.isAuthenticated(request, { successRedirect: "/profile" });
  let session = await sessionStorage.getSession(request.headers.get("Cookie"));
  return json({
    magicLinkSent: session.has("auth:magiclink"),
    magicLinkEmail: session.get("auth:email"),
  });
};

export let action = async ({ request }) => {
  await auth.authenticate("email-link", request, {
    // successRedirect: "/",
  });
};

With successRedirect commented out, the code will throw the following error:

Error: Missing successRedirect. The successRedirect is required for POST requests.

Expected behavior

My login is through a dropdown, so the user can login on any page, simply by clicking the dropdown in the nav menu. Therefore, it is disruptive to the user to have the page be redirected to another page when they request a login email.

Screenshots or Videos

No response

Platform

  • OS: MacOS
  • Browser: Brave
  • Version: Version 1.50.121 Chromium: 112.0.5615.138 (Official Build) (arm64)

Additional context

No response

Unable to `npm install` or `yarn install` from a fresh repo clone: unable to resolve dependency tree error

Describe the bug

After cloning the repo locally and running npm install there is the following error:

npm install
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! 
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/react
npm ERR!   dev react@"^17.0.2" from the root project
npm ERR!   peer react@">=16.8" from @remix-run/[email protected]
npm ERR!   node_modules/@remix-run/react
npm ERR!     dev @remix-run/react@"^1.1.1" from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^18.2.0" from [email protected]
npm ERR! node_modules/react-dom
npm ERR!   peer react-dom@">=16.8" from @remix-run/[email protected]
npm ERR!   node_modules/@remix-run/react
npm ERR!     dev @remix-run/react@"^1.1.1" from the root project
npm ERR! 
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.

Your Example Website or App

https://github.com/pbteja1998/remix-auth-email-link

Steps to Reproduce the Bug or Issue

  1. Clone the repo
  2. Navigate to the root of the repo
  3. Run npm install

Expected behavior

It should install the dependencies without having to use the --legacy-peer-deps option

Screenshots or Videos

No response

Platform

  • OS: macOS
  • npm --version = 8.15.0
  • node --version = 16.17.0

Additional context

No response

User is not immediately authenticated after using magic link

Hello,
I'm using the email link strategy, my setup is nearly identical to the one from readme. Sometimes, user isn't immediately authenticated after visiting magic link. In magic route I'm redirecting him to authorization protected page, and then he is redirected back to login because he isn't yet authenticated. After refresh everything works and he is redirected out from login page.

These are logs from my express server:
image

As you can see, user is successfully redirected to /me, but then authorization fails and redirect to /login kicks in.

Repository: https://github.com/pawelblaszczyk5/planotes
Live: https://planotes.onrender.com/

Can't get email from form for use in successful submission message

I'd like to store the email address from the submitted form and use it to display a helpful message like:

Magic link sent to ${email}!

This message would be displayed after successful submission of the magic link login form, before the user actually clicks the magic link in his email.

... but I don't see a way to do that right now without writing my own .authenticate function. Any help is appreciated. Thank you for creating this project from the old code!

Sending magic link successfully redirects to failureRedirect not successRedirect

Hi,

So the behavior of my app works exactly as expected except for the redirects when sending the email link. Instead of redirecting to my set successRedirect I am redirected to the failureRedirect. This occurs even though my sendEmail function does not throw any exceptions or errors. I am not sure what I am doing wrong.

sendEmail:

export let sendEmail: SendEmailFunction<User> = async (options) => {
  let subject = "Log into Outryder";
  let html = renderToString(
    <p>
      Hey { options.user?.name || "there" }!<br/>
      <br />
      <a href={options.magicLink}>Click here to login.</a>
    </p>
  );

  let transporter = nodemailer.createTransport(
    nodemailerSendgrid({
      apiKey: process.env.MAGIC_LINK_API_KEY!
    })
  );

  await transporter.sendMail({
    from: "[email protected]",
    to: options.emailAddress,
    subject,
    html
  });
}

Login page:

export let loader: LoaderFunction = async ({ request }) => {
  await authenticator.isAuthenticated(request, { successRedirect: "/dashboard "});
  let session = await getSession(request.headers.get("Cookie"));

  return json({
    magicLinkSend: session.has("auth:magiclink")
  });
};

export let action: ActionFunction = async ({ request }) => {
  await authenticator.authenticate("email-link", request, {
    successRedirect: "/auth/verify",
    failureRedirect: "/auth/verify?error=true"
  });
};

export default function SignIn() {
  let { magicLinkSent } = useLoaderData<{ magicLinkSent: boolean }>()
  
  return (
    <Form action="/signin" method="post">
      has send email: { magicLinkSent ? "true" : "false" }.
      <div>
        <label htmlFor="email">Email Address</label>
        <input id="email" type="email" name="email" required />
      </div>
      <button type="submit">Submit</button>
    </Form>
  );
}

So I get redirected to /auth/verify?error=true. Furthermore, when I had the failureRedirect to be the same as the login link, the magicLinkSent parameter wasn't updated.

sessionMagicLinkKey default value typo

Describe the bug

The class documentation tells the default value for sessionMagicLinkKey uses camelCase.

  /**
   * The key on the session to store the magic link.
   * @default "auth:magicLink"
   */
  sessionMagicLinkKey?: string

The code examples in the README use lowercase.

  // This session key `auth:magiclink` is the default one used by the EmailLinkStrategy
  // you can customize it passing a `sessionMagicLinkKey` when creating an
  // instance.
  return json({
    magicLinkSent: session.has('auth:magiclink'),
    magicLinkEmail: session.get('auth:email'),
  })

Here we can see, that lowercase is the right way to go if you want make things work.
https://github.com/pbteja1998/remix-auth-email-link/blob/main/src/index.ts#L173

this.sessionMagicLinkKey = options.sessionMagicLinkKey ?? 'auth:magiclink'

So, I suppose either the docs or the default value should be corrected.
Otherwise some people may have quite a bit of fun debugging their auth code, trying to figure out why the link is not there.

Your Example Website or App

Does not really need stackblitz

Steps to Reproduce the Bug or Issue

  1. Look here https://github.com/pbteja1998/remix-auth-email-link/blob/main/src/index.ts#L100
  2. Then look here https://github.com/pbteja1998/remix-auth-email-link/blob/main/src/index.ts#L173

Expected behavior

  • I would expect to actually get the magic link value without surprises

Screenshots or Videos

No response

Platform

  • OS: macOS
  • Browser: Chrome
  • Version: 110.0.5481.177

Additional context

No response

adapting the strategy to send a token instead of an email link

Hi Bhanu, I just wanted to check in with you to ask you how feasible you think it would be to adapt this strategy to email the user a token which they would enter into the app to authenticate? Do you think it's worth me adapting it or would it be better for me to build a separate strategy?
Thanks!

Error: Cookie length will exceed browser maximum. Length: number

Describe the bug

As a user, I am receiving the email with the magic link, but production gave me the following error message after submitting the form with the email.

Error: Cookie length will exceed browser maximum. Length: 4503

after calling the function:

  await auth.authenticate("email-link", request, {
    successRedirect: "/auth/login",
    // If this is not set, any error will be throw and the ErrorBoundary will be
    // rendered.
    failureRedirect: "/auth/login",
  });

This is the track trace:

The throw new Error("Cookie length will exceed browser maximum. Length: " + serializedCookie.length); method in the remix createCookieSessionStorageFactory function

/**
 * Creates and returns a SessionStorage object that stores all session data
 * directly in the session cookie itself.
 *
 * This has the advantage that no database or other backend services are
 * needed, and can help to simplify some load-balanced scenarios. However, it
 * also has the limitation that serialized session data may not exceed the
 * browser's maximum cookie size. Trade-offs!
 *
 * @see https://remix.run/utils/sessions#createcookiesessionstorage
 */
const createCookieSessionStorageFactory = createCookie => ({
  cookie: cookieArg
} = {}) => {
  let cookie = cookies.isCookie(cookieArg) ? cookieArg : createCookie((cookieArg === null || cookieArg === void 0 ? void 0 : cookieArg.name) || "__session", cookieArg);
  sessions.warnOnceAboutSigningSessionCookie(cookie);
  return {
    async getSession(cookieHeader, options) {
      return sessions.createSession(cookieHeader && (await cookie.parse(cookieHeader, options)) || {});
    },
    async commitSession(session, options) {
      let serializedCookie = await cookie.serialize(session.data, options);
      if (serializedCookie.length > 4096) {
        throw new Error("Cookie length will exceed browser maximum. Length: " + serializedCookie.length);
      }
      return serializedCookie;
    },
    async destroySession(_session, options) {
      return cookie.serialize("", {
        ...options,
        expires: new Date(0)
      });
    }
  };
};

In the function of the library authenticate and in the line const cookie = await sessionStorage.commitSession(session);

    async authenticate(request, sessionStorage, options) {
        var _a;
        const session = await sessionStorage.getSession(request.headers.get('Cookie'));
        const form = new URLSearchParams(await request.text());
        // This should only be called in an action if it's used to start the login process
        if (request.method === 'POST') {
            if (!options.successRedirect) {
                throw new Error('Missing successRedirect. The successRedirect is required for POST requests.');
            }
            // get the email address from the request body
            const emailAddress = form.get(this.emailField);
            // if it doesn't have an email address,
            if (!emailAddress || typeof emailAddress !== 'string') {
                const message = 'Missing email address.';
                if (!options.failureRedirect) {
                    throw new Error(message);
                }
                session.flash(this.sessionErrorKey, { message });
                const cookie = await sessionStorage.commitSession(session);
                throw (0, server_runtime_1.redirect)(options.failureRedirect, {
                    headers: { 'Set-Cookie': cookie },
                });
            }
            try {
                // Validate the email address
                await this.validateEmail(emailAddress);
                const domainUrl = this.getDomainURL(request);
                const magicLink = await this.sendToken(emailAddress, domainUrl, form);
                session.set(this.sessionMagicLinkKey, await this.encrypt(magicLink));
                session.set(this.sessionEmailKey, emailAddress);
                throw (0, server_runtime_1.redirect)(options.successRedirect, {
                    headers: {
                        'Set-Cookie': await sessionStorage.commitSession(session),
                    },
                });
            }
            catch (error) {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                if (error.status === 302) {
                    // If it's a redirect, then just throw the redirect as it is
                    throw error;
                }
                if (!options.failureRedirect) {
                    throw error;
                }
                const { message } = error;
                session.flash(this.sessionErrorKey, { message });
                const cookie = await sessionStorage.commitSession(session);
                throw (0, server_runtime_1.redirect)(options.failureRedirect, {
                    headers: { 'Set-Cookie': cookie },
                });
            }
        }
        let user;
        try {
            // If we get here, the user clicked on the magic link inside email
            const magicLink = (_a = session.get(this.sessionMagicLinkKey)) !== null && _a !== void 0 ? _a : '';
            const { emailAddress: email, form } = await this.validateMagicLink(request.url, await this.decrypt(magicLink));
            // now that we have the user email we can call verify to get the user
            user = await this.verify({ email, form, magicLinkVerify: true });
        }
        catch (error) {
            // if something happens, we should redirect to the failureRedirect
            // and flash the error message, or just throw the error if failureRedirect
            // is not defined
            if (!options.failureRedirect) {
                throw error;
            }
            const { message } = error;
            session.flash(this.sessionErrorKey, { message });
            const cookie = await sessionStorage.commitSession(session);
            throw (0, server_runtime_1.redirect)(options.failureRedirect, {
                headers: { 'Set-Cookie': cookie },
            });
        }
        if (!options.successRedirect) {
            return user;
        }
        // remove the magic link and email from the session
        session.unset(this.sessionMagicLinkKey);
        session.unset(this.sessionEmailKey);
        session.set(options.sessionKey, user);
        const cookie = await sessionStorage.commitSession(session);
        throw (0, server_runtime_1.redirect)(options.successRedirect, {
            headers: { 'Set-Cookie': cookie },
        });
    }

Is any way to print all the values of the session to check if for some reason I am putting external data?

Your Example Website or App

https://aa8d-190-135-204-71.ngrok-free.app/auth/login

Steps to Reproduce the Bug or Issue

  1. Using the library fill the email, and then trigger the authentication process.

Expected behavior

As a user, I am receiving the email with the magic link, but production gave me the following error message after submitting the form with the email.
Error: Cookie length will exceed browser maximum. Length: 4503

Screenshots or Videos

Screenshot 2023-06-02 at 5 21 36 PM Screenshot 2023-06-02 at 5 21 43 PM

Platform

  • OS: [macOS]
  • Browser: [Chrome, Safari]
  • Version: [113.0,16.5]

Additional context

No response

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.