Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Passwordless login without DB (using emailed code) #1913

Closed
benderillo opened this issue May 3, 2021 · 12 comments
Closed

Passwordless login without DB (using emailed code) #1913

benderillo opened this issue May 3, 2021 · 12 comments
Labels
enhancement New feature or request stale Did not receive any activity for 60 days

Comments

@benderillo
Copy link

benderillo commented May 3, 2021

Summary 💭

Add support for passwordless authentication flow that involves sending a random code to the user's email and asking them to type it into the login form on the frontend in order to get a JWT. This flow does not require DB.

Description 📓

DISCLAIMER: I am not a security expert and my proposal could be flawed with security concerns.

Presently next-auth can be used without a DB for OAuth flows that social login providers offer. But if one opts for passwordless(i.e. email provider), then the DB is required to store the requests.
I understand that this is necessary requirement because the "magic link" is issued and when user clicks it, we need to match it with the request on the backend.

There is another popular passwordless login flow that requires a user to type in a code that is sent to their email.
Sites that use this UX: workflowy.com, stripe.com

It is my understanding that this flow can be done without a Database.
This is how I envision this designed:
Frontend code (further a Client) interacts with the server-side function (serverless functions) (further Backend).

  1. A Client sends an email address to the Backend
  2. The Backend generates random code, hashes the object that contains user email and the code all salted with some secret key and returns the hash back to the client (as HttpOnly secure cookie with configured expiration time).
const hash = hash({email: "[email protected]", code: "3849-0928-1237"}, salt);
  1. The Backend sends an email with the code value to the given email address
  2. The Client receives the hash renders the view asking a user to enter the code
  3. The User enters the code that arrives in their mailbox
  4. The Client sends to the Backend the following data: {code, email}
  5. The Backend verifies that cookie with the hash has not expired;
  6. The Backend validates the {code, email} by comparing hash with the one in the cookie.
  7. If hash matches it issues access_token (JWT)
  • purpose of the feature
    Passwordless auth is modern way of dealing with Auth.
    If NextAuth support it without the DB, then the site can be deployed on Vercel or Netlify without the need to provision a database hosting.

One of the purposes of this request is to get it reviewed by someone who understands security and authentication well enough to tell if this will fly or crash and burn.

  • potential problems
    As I mentioned, I am not a security expert and there maybe severe security concerns that I am not aware of.

  • potential alternatives
    The existing email provider and the database.

@benderillo benderillo added the enhancement New feature or request label May 3, 2021
@balazsorban44
Copy link
Member

#1051 touched on this same idea, and @iaincollins gave a good answer why we don't support it: #1051 (comment)

That said, he also mentioned that we could provide it as an alternative. My problem with it would be that it will only work on the same device, while the current solution will work across devices.

@benderillo
Copy link
Author

benderillo commented May 4, 2021

That said, he also mentioned that we could provide it as an alternative. My problem with it would be that it will only work on the same device, while the current solution will work across devices.

@balazsorban44 This is actually my preferred way for passwordless to work :) I am bit paranoid about the idea that clicking the link can happen on any device so long as the token is still valid. I am finding the "code" flow safer.

I read the comment but the intent or rationale of the the #1051 is different :)
I brought this up because it seems to be doable without a DB. The #1051 asks for extra complexity without significant benefit.
I propose a flow that eliminates Database - big hurdle in bringing NextAuth passwordless to an application with pre-existing API/backend service.

Basically, when you can't co-locate your NextJS frontend and existing API service (which is not uncommon), one is left with a few options:

  • Setup another DB and provide NextAuth with RW access to that DB. (Cons: Extra $$$)
  • Write custom DB adapter that under the hood talks to some service that would interact with the DB sitting in private subnet comfortably. (Cons: extra development and maintenance effort).
  • Open up existing private DB (say it was initially in private subnet on AWS but now you put it in public subnet and whitelist the IP range that can access it).

If there was a passwordless flow without DB, one could hook up existing API service to NextAuth using a couple of those callbacks (signIn and jwt) to register users when needed and fetch tokens(optionally).

@stale
Copy link

stale bot commented Jul 3, 2021

Hi there! It looks like this issue hasn't had any activity for a while. It will be closed if no further activity occurs. If you think your issue is still relevant, feel free to comment on it to keep it open. (Read more at #912) Thanks!

@stale stale bot added stale Did not receive any activity for 60 days and removed stale Did not receive any activity for 60 days labels Jul 3, 2021
@benderillo
Copy link
Author

Hi there! It looks like this issue hasn't had any activity for a while. It will be closed if no further activity occurs. If you think your issue is still relevant, feel free to comment on it to keep it open. (Read more at #912) Thanks!

I comment on it :)

@narciero
Copy link
Contributor

narciero commented Jul 9, 2021

I ran into a similar issue and opted to use Magic links, I wrote a post about how to integrate Magic with Next-Auth here:

https://dev.to/narciero/using-nextauth-js-with-magic-links-df4

@benderillo
Copy link
Author

benderillo commented Jul 10, 2021

I ran into a similar issue and opted to use Magic links, I wrote a post about how to integrate Magic with Next-Auth here:

https://dev.to/narciero/using-nextauth-js-with-magic-links-df4

I don't really have issues with Next-Auth. This ticket is a proposal for another Auth flow to be part of the Next-Auth itself.
Using Magic is different because it is not about adding capabilities to the Next-Auth.

Here is the thing with the "Magic" or any other provider like Auth0 or Okta really: you essentially opt to use a commercial service to do your auth with all its advantages. But you also accept that you are picking a paid product that is going to cost you. Sure, using Magic frees you from setting up a DB, but there IS a DB, it's just handled by the "Magic". And who is going to pay for this? That's right, you. And not only DB, you will pay for all their devs, marketing, sales, and whatever else is included in the bill. If you are fine to do so in exchange to a painless setup, great, but this is not for everyone.

Next-Auth is not just another Auth service, it does not charge you. It is just a software package that you host on your own infra hence you pay for what you run anyways.

@ubbe-xyz
Copy link
Collaborator

I think there's already an initiative to do this: #1465, we just need to think carefully how we're gonna implement it, I think we could close this issue and follow up in the discussion? 💡

@giuseppeg
Copy link

giuseppeg commented Aug 14, 2021

I proposed a similar idea in #2526 and I would be happy to implement a proof of concept if the maintainers can give me some pointers – mainly I would like to know if this can be implemented as a standalone external provider or if I'd need to hardcode the changes in Next Auth.

Ideally a databaseless implementation of the Email provider would support both JWT and Database sessions. In my specific case I have a database but I would like to not use it for magic links in order to save resources and to avoid having to clean up the tokens table periodically (remove unused tokens).

@stale
Copy link

stale bot commented Oct 13, 2021

Hi there! It looks like this issue hasn't had any activity for a while. It will be closed if no further activity occurs. If you think your issue is still relevant, feel free to comment on it to keep it open. (Read more at #912) Thanks!

@stale stale bot added the stale Did not receive any activity for 60 days label Oct 13, 2021
@stale
Copy link

stale bot commented Oct 20, 2021

Hi there! It looks like this issue hasn't had any activity for a while. To keep things tidy, I am going to close this issue for now. If you think your issue is still relevant, just leave a comment and I will reopen it. (Read more at #912) Thanks!

@stale stale bot closed this as completed Oct 20, 2021
@Izhaki
Copy link

Izhaki commented Nov 22, 2021

Without DB

I wonder @benderillo how this proposal (without a DB) works with regards to the user ID or name?

If no DB is involved, then the only user identifier is their email address. This may do to sign in users, but what about these cases:

  • Users do not want their email on screen.
  • Users need to see other users (like in this GitHub thread - I'm identified by my username), users on public sites may not wish their email to be public.

Point is, this is useful for a handful of scenarios/apps and is hardly the common use case.

With DB

Although this issue is about passwordless login using emailed code without DB, I have managed to implement passwordless login using emailed code (ie, no magic link) with DB. I'm describing how in this issue as it what a search for "passwordless email code".

This is similar to how Instagram signs you in if you forgot your password.

I wonder @balazsorban44 if this is something we want to support out-of-the-box (by extending Email Provider) or should this become a tutorial?

next-auth-passwordless-code

1. Modify Token Generation Code

In the EmailProvider config provide a custom generateVerificationToken to produce a shorter token (typically 6 digit number).

2. Modify Email

In the EmailProvider config provide a custom sendVerificationRequest so instead of a magic link the email shows the token itself.

Note: sendVerificationRequest does get the token as a parameter (just like url) - this is not documented everywhere.

3. Custom Sign In page

Edit the pages section of the NextAuth config to point to a new sign in page:

    pages: {
      signIn: "/auth/signin", 
    }

And here is the sign in page code (no error handling on call to signIn yet):

// pages/auth/signin.js

import * as React from "react";
import { signIn } from "next-auth/react";

function getCallbackUrl() {
  const urlParams = new URLSearchParams(window.location.search);
  return urlParams.get("callbackUrl");
}

function EmailForm({ onSubmit }) {
  const [email, setEmail] = React.useState("");

  const handleSignIn = async () => {
    await signIn("email", { email, redirect: false });
    onSubmit(email);
  };

  return (
    <div>
      <label htmlFor="email">Email</label>
      <input
        type="email"
        id="email"
        name="email"
        value={email}
        onChange={(event) => {
          setEmail(event.target.value);
        }}
      />
      <button onClick={handleSignIn}>Send Login Code</button>
    </div>
  );
}

function CodeForm({ email }) {
  const [token, setToken] = React.useState("");

  const urlParams = new URLSearchParams({
    email,
    token,
    callbackUrl: getCallbackUrl(),
  });

  return (
    <form method="post" action={`/api/auth/callback/email?${urlParams}`}>
      <label htmlFor="token">Enter the code emailed to you:</label>{" "}
      <input
        type="text"
        id="token"
        name="token"
        value={token}
        onChange={(event) => {
          setToken(event.target.value);
        }}
      />
      <button type="submit">Sign In</button>
    </form>
  );
}

export default function SignIn() {
  const [email, setEmail] = React.useState(null);

  return (
    <div className="signin page">
      <div className="card">
        {email ? <CodeForm email={email} /> : <EmailForm onSubmit={setEmail} />}
      </div>
    </div>
  );
}

@balazsorban44
Copy link
Member

I believe someone already created a tutorial, but please go for it! #709 (comment)

mmkal added a commit to mmkal/next-auth that referenced this issue Jul 3, 2022
I'm roughly following/adapting this guide for sending a six-digit code via email: https://www.ramielcreations.com/nexth-auth-magic-code

I would like to use it in a stack where we're using Amazon SES as an emailing abstraction, rather than an SMTP server directly. And while developing, it has been useful to just `console.log` the verification code in a custom `sendVerificationRequest` implementation. One snag I hit, though, was that not having the `nodemailer` dependency installed caused an error just by requiring the `EmailProvider`, even though it's only used in one place, that I was overriding anyway. I've seen in nextauthjs#1913 and some other related issues/discussions that there may be willingness to create a non-email-biased `PasswordlessProvider` in future. But for now this step is a really simple code change to make and decreases the `nodemailer` dependency without needing to create a whole new provdier.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request stale Did not receive any activity for 60 days
Projects
None yet
Development

No branches or pull requests

6 participants