GithubHelp home page GithubHelp logo

mxstbr / passport-magic-login Goto Github PK

View Code? Open in Web Editor NEW
660.0 7.0 45.0 416 KB

Passwordless authentication with magic links for Passport.js.

License: MIT License

TypeScript 100.00%
passport express authentication magiclink

passport-magic-login's People

Contributors

andyrichardson avatar enberg avatar eric-burel avatar jackca avatar maraisr avatar marcusvmsa avatar mxstbr avatar ramrami avatar rfbowen avatar tmkn 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

passport-magic-login's Issues

What happen, when i open the link on phone?

Hi @mxstbr
thank you very much for this passport strategy to support magic links. I would like to use it in my webapp. But what would happen if the user opens the link on phone instead of desktop? Would the user still be logged in on the desktop because he sent the request from there?

sendLink href has added undefined

The href being generated by the sendMagicLink function seems be adding 'undefined' before the question mark and token=.
Here is my strategy:

const magicLogin = new MagicLoginStrategy({
  secret: process.env.MAGIC_LINK_SECRET,
  callbackURL: '/auth/magiclogin/callback',
  sendMagicLink: async (destination, href) => {
    try {
      console.log(href)
      await sendEmail(destination.email, destination.givenName, href);
    } catch (error) {
      console.error(error);
    }
  },
  verify: async (payload, callback) => {
    try {
      const dbUser = await prisma.user.upsert({
        where: {
          email: payload.destination,
        },
        update: {
          updatedAt: new Date(Date.now()),
        },
        create: {
          data: {
            email: payload.destination,
            emailVerified: new Date(Date.now()),
          },
        },
        include: {
          employee: {
            select: {
              userId: true,
              isAdmin: true,
            },
          },
        },
      });
      return callback(null, dbUser);
    } catch (error) {
      return callback(error);
    }
  },
});
passport.use(magicLogin);

router.post('/sendlink', magicLogin.send);
router.get('/magiclogin/callback', passport.authenticate('magiclogin'));

The href that is passed to my sendMail function looks like this:

undefined?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXN0aW5hdGlvbiI6eyJlbWFpbCI6ImVyaWMuYnJhbnNvbkBnbWFpbC5jb20iLCJnaXZlbk5hbWUiOiJFcmljIn0sImNvZGUiOiI5OTE4MSIsImlhdCI6MTY3NjA0MzMwOCwiZXhwIjoxNjc2MDQ2OTA4fQ.9vAUmm1pAPIX-tJq8qE3HK-mRY-ZMH7qeJByIqO2RWo

I can remove the undefined but I don't know how it got there in the first place.

This is all in windows 10 in Node 18.12.1

[Security Risk] Dangerous flow when JWT is incorrect or missing

Problem description

If JWT token in an authentication link is missing, is incorrect or its signature is incorrect then false is passed to the verify method. This flow seems not to be expected as even the library documentation on the passport page here https://www.passportjs.org/packages/passport-magic-login/ does not seem to handle this situation properly assuming payload.destination always exists.

Possible security implication

When false is passed then payload.destination is equal to undefined. If this is not handled and payload.destination is then passed to an ORM or ODM while looking for the user in a database this can lead to the situation in which undefined is passed directly to the where clause of the database query. In this case most ORMs like TypeORM will assume that the query should not have a where clause at all and will return first user from the database. This leads to login without access to user's email and bypassing whole application security.

Proof of concept

When application is using following verify method

verify: (payload, callback) => {
    const user = userRepository.findOne({
        where: { email: payload.destination },
    });
    
    if(user)
        callback(null, user)
    else 
        callback("user not found")
}

and the callback parameter is empty or contains malformed JWT token, the application will login user as a first user in the database.

Proposed mitigation

  1. Throw an error and break the application flow if JWT token provided with the link is incorrect or empty
  2. Mark in the documentation that payload parameter in the verify method should be checked if equal to false

fastify support

Please change
res.json({ success: true, code: code });

to
res.send({ success: true, code: code });

to make it work on both express and fastify router

Make the code part in send customizable / overrideable

The current 5 char generated code can be brute forced, if it were possible to make it a lot longer / overrideable it would be possible to use magicLogin in a way that doesn't require the clicking of the link on the same device.

Something like this:

send = (req: Request, res: Response,code?: string): void => {
    const payload = req.method === 'GET' ? req.query : req.body;
    if (
      req.method === 'POST' &&
      !req.headers['content-type']?.match('application/json')
    )
 {
      res
        .status(400)
        .send('Content-Type must be application/json when using POST method.');
      return;
    } if (!payload.destination) {
      res.status(400).send('Please specify the destination.');
      return;
    }

    if (!code){
      code = Math.floor(Math.random() * 90000) + 10000 + '';
    }

Send magic link without API call?

Thank you for your great work on this strategy!

Is there an existing way to send or generate a magic link using just JavaScript, without needing to do it via a POST/GET call (which the current send() method is specifically designed to do)?

It would be great to be able to send or generate magic links from within a custom API endpoint, for example:

  • magicLogin.send('[email protected]') to send an email notification to a user telling them that a document is available that included a magic link that takes them straight to it; or
  • magicLogin.getMagicLink('[email protected]') to generate a magic link URL to be used elsewhere

Pass more data at signup

First of all, thank you this package is awesome 😍

I would have one question though: Is it possible to pass more data at signup than just the email address (like a username, language, etc.) or is it an anti-pattern with this way of authenticated a user?

export types

any chance of exporting the VerifyCallback and Options types? typescript isn't inferring the super class at all when used as part of a nestjs implementation...

import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import Strategy from "passport-magic-login";

@Injectable()
export class MagicLoginStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({...} as Options); // <-- would be nice to cast to the Option type here instead of digging through the type declarations
  }
}

TypeError: MagicLoginStrategy is not a constructor

Hello,

While literally copy pasting your code, I get a error message that the MagicLoginStrategy is not a constructor. Why is this happening?

const magicLogin = new MagicLoginStrategy({
      ....
})

// Add the passport-magic-login strategy to Passport
// passport.use(magicLogin)

Thanks!

Automatically route with express

Amazing project! A feature request :)

The current documentation reads "Once you've got that, you'll then need to add a couple of routes to your Express server:"

// Add the passport-magic-login strategy to Passport
passport.use(magicLogin)

// This is where we POST to from the frontend
app.post("/auth/magiclogin", magicLogin.send);

// This is what the user visits to confirm the login attempt and redirects them to the callbackUrl
app.get(magicLogin.confirmUrl, magicLogin.confirm);

// This is the standard Passport callbackUrl thing
app.get(magicLogin.callbackUrl, passport.authenticate("magiclogin"));

But we can instead make it a bit simpler to the user like this:

app.use(magicLogin(passport));

Then inside magicLogin, we register the passport.use() and the different routes. This way the user doesn't need to do all of this scaffolding by themselves and the only work needed is:

import MagicLoginStrategy from "passport-magic-login"

const magicLogin = new MagicLoginStrategy({
  // all the config
})

app.use(magicLogin(passport));

This is IMHO a much cleaner API.

No feature request is complete without the cons, so the main issue I see of this is the lack of transparency on what is going on internally. But this is probably okay since the configuration key names are self-explanatory + there's documentation pointing out at this.

Edge Runtime compatibility

Any timeframe for this becoming compatible with Edge runtime?
I am trying to use and I get the following Typescript error:

TS2345: Argument of type '(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>) => void' is not assignable to parameter of type 'Nextable<RequestHandler<NextRequest, NextFetchEvent>> | RouteMatch'.   Type '(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>) => void' is not assignable to type 'Nextable<RequestHandler<NextRequest, NextFetchEvent>>'.     Types of parameters 'req' and 'args_0' are incompatible.       Type 'NextRequest' is missing the following properties from type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>': get, header, accepts, acceptsCharsets, and 83 more.

token.ts expiry

Hello, is it possible to specify an expiration period for tokens? I'm seeing expiresIn: '60min' in your token.ts. I'd love to make that a bit longer (for a specific use case)

Can clicking the link be skipped when creating a new account?

When someone needs to log back in to an existing account, it makes sense to ask them to click the magic link to prove they own an email.

However, when someone is creating a new account for the first time using a new email, it would be nice to have the option to log them in directly without requiring that extra step of actually clicking the magic link. Of course, the email address wouldn't be verified, but I think in some cases it can be worth it just for lowering the friction of creating an account?

Is there a way this can be accomplished currently, or could it maybe be added as an option?

error TS2351: This expression is not constructable.

I get the following error when trying to use it with typescript:

error TS2351: This expression is not constructable.
  Type 'typeof import("/Users/guiguiguigui93/Progetti/aibattle/node_modules/passport-magic-login/dist/index")' has no construct signatures.

my code

const magicLogin = new MagicLoginStrategy({
    secret: process.env.MAGIC_LINK_SECRET,
    callbackUrl: "/auth/magiclogin/callback",
    sendMagicLink: async (destination, href) => {
        console.log("sendMagicLink", destination, href);
    },
    verify: (payload, callback) => {
        console.log("verify", payload);
    },
    jwtOptions: {
        expiresIn: "2 days",
    }
})

What's the best strategy to have sendMagicLink fail?

I'd like to use sendMagicLink only to pre-registered users.
At the moment, I'm throwing an error if the submitted email isn't in the database.

This prints something on my server console because of this line:

console.error(error);

Is that expected? I understand that sendMagicLink might fail for unexpected reasons, but I would have hoped that somehow there was a more elegant way so that no "intended" error is printed in the console.

Unable to user req as NextApiRequest

Hello i'm try to use passport-magic-login in a nextJS application.

in my call back i have this

export default async function handler(req: NextApiRequest, res: NextApiResponse) { passport.authenticate('magiclogin',async (err:any, payload:any) => { // ... doing auth stuff res.redirect('/test') })(req,res) }

But it throw an 500 error with next is not a function.

Does the frontend need to verify the email address for security purposes?

Hi!

Thanks for your great library! And sorry if this is a beginners questions and I am missing something...

But I was comparing this solution to Firebase's implementation of "email link authentication". By their documentation they require the frontend to store the entered email in localStorage so they can verify it against the authenticated user token for security purposes. If the user opens the email on another device they force the user to input their original email address. This is "To prevent a sign-in link from being used to sign in as an unintended user or on an unintended device" according to their documentation.

Is this a security concern that should be addressed when using your library (or is it already included perhaps and I'm missing something)?

Thanks again for a great passport library 🏆

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.